diff --git a/.gitignore b/.gitignore index 9269cefc51fa0..b7f681b292e4d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,12 @@ atlassian* /lib/internal/flex/varien/.settings /node_modules /.grunt +/Gruntfile.js +/package.json /pub/media/*.* !/pub/media/.htaccess +/pub/media/analytics/* /pub/media/catalog/* !/pub/media/catalog/.htaccess /pub/media/customer/* @@ -49,3 +52,5 @@ atlassian* !/var/.htaccess /vendor/* !/vendor/.htaccess +/generated/* +!/generated/.htaccess diff --git a/.php_cs b/.php_cs index 5bd1a01537611..4bd705bb09a2f 100644 --- a/.php_cs +++ b/.php_cs @@ -1,6 +1,6 @@ Welcome Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting edge, feature-rich eCommerce solution that gets results. -The installation instructions that used to be here are now published on our GitHub site. Use the information on this page to get started or go directly to the guide. +## Magento system requirements +[Magento system requirements](http://devdocs.magento.com/magento-system-requirements.html) -

New to Magento? Need some help?

-If you're not sure about the following, you probably need a little help before you start installing the Magento software: +## Install Magento +To install Magento, see either: -* Is the Magento software installed already? -* What's a terminal, command prompt, or Secure Shell (ssh)? -* Where's my Magento server and how do I access it? -* What's PHP? -* What's Apache? -* What's MySQL? - -

Step 1: Verify your prerequisites

- -Use the following table to verify you have the correct prerequisites to install the Magento software. - - - - - - - - - - - - - - - - - - - - - - - -
PrerequisiteHow to checkFor more information
Apache 2.2 or 2.4Ubuntu: apache2 -v
- CentOS: httpd -v
Apache
PHP 5.6.x, 7.0.2 or 7.0.6php -vPHP Ubuntu
PHP CentOS
MySQL 5.6.xmysql -u [root user name] -pMySQL
- -

Step 2: Prepare to install

- -After verifying your prerequisites, perform the following tasks in order to prepare to install the Magento software. - -1. Install Composer -2. Clone the Magento repository - -

Step 3: Install and verify the installation

- -1. Update installation dependencies -2. Install Magento: - * Install Magento software using the web interface - * Install Magento software using the command line -2. Verify the installation +* [Magento DevBox](https://magento.com/tech-resources/download), the easiest way to get started with Magento. +* [Installation guide](http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html)

Contributing to the Magento 2 code base

Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. diff --git a/app/autoload.php b/app/autoload.php index b817bafa7fa4c..6fdfcd07fdff1 100644 --- a/app/autoload.php +++ b/app/autoload.php @@ -2,7 +2,7 @@ /** * Register basic autoloader that uses include path * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ use Magento\Framework\Autoload\AutoloaderRegistry; diff --git a/app/bootstrap.php b/app/bootstrap.php index ec60a1708dacc..eed7b6d798ad3 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -1,6 +1,6 @@ = 50600 && PHP_VERSION_ID < 50700 || PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { +if (!defined('PHP_VERSION_ID') || !(PHP_VERSION_ID >= 50605 && PHP_VERSION_ID < 50700 || PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { if (PHP_SAPI == 'cli') { - echo 'Magento supports PHP 5.6, 7.0.2, 7.0.4, and 7.0.6 or later. ' . + echo 'Magento supports PHP 5.6.5, 7.0.2, 7.0.4, and 7.0.6 or later. ' . 'Please read http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html'; } else { echo << -

Magento supports PHP 5.6, 7.0.2, 7.0.4, and 7.0.6 or later. Please read +

Magento supports PHP 5.6.5, 7.0.2, 7.0.4, and 7.0.6 or later. Please read Magento System Requirements. @@ -35,6 +35,17 @@ $mask = file_exists($umaskFile) ? octdec(file_get_contents($umaskFile)) : 002; umask($mask); +if (empty($_SERVER['ENABLE_IIS_REWRITES']) || ($_SERVER['ENABLE_IIS_REWRITES'] != 1)) { + /* + * Unset headers used by IIS URL rewrites. + */ + unset($_SERVER['HTTP_X_REWRITE_URL']); + unset($_SERVER['HTTP_X_ORIGINAL_URL']); + unset($_SERVER['IIS_WasUrlRewritten']); + unset($_SERVER['UNENCODED_URL']); + unset($_SERVER['ORIG_PATH_INFO']); +} + if (!empty($_SERVER['MAGE_PROFILER']) && isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false @@ -47,3 +58,7 @@ } date_default_timezone_set('UTC'); + +/* Adjustment of precision value for several versions of PHP */ +ini_set('precision', 17); +ini_set('serialize_precision', 17); diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php index 322c61b32a534..a7b11138628a4 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php @@ -2,7 +2,7 @@ /** * Adminhtml AdminNotification Severity Renderer * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Notice.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Notice.php index b370cbd8b1a6f..aa95028cfb4a0 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Notice.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Notice.php @@ -2,7 +2,7 @@ /** * Adminhtml AdminNotification Severity Renderer * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\AdminNotification\Block\Grid\Renderer; diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Severity.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Severity.php index 7d0ccd163b8e0..c348b6cc76101 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Severity.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Severity.php @@ -2,7 +2,7 @@ /** * Adminhtml AdminNotification Severity Renderer * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\AdminNotification\Block\Grid\Renderer; diff --git a/app/code/Magento/AdminNotification/Block/Inbox.php b/app/code/Magento/AdminNotification/Block/Inbox.php index 899d141c9b07f..984de28fd4b94 100644 --- a/app/code/Magento/AdminNotification/Block/Inbox.php +++ b/app/code/Magento/AdminNotification/Block/Inbox.php @@ -2,7 +2,7 @@ /** * Adminhtml AdminNotification inbox grid * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\AdminNotification\Block; diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index 223941c6e43f7..db2ecf62f0685 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/di.xml b/app/code/Magento/AdminNotification/etc/adminhtml/di.xml index 8986ce73472e5..1be5f99616cc3 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/di.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/events.xml b/app/code/Magento/AdminNotification/etc/adminhtml/events.xml index 07bd1ae5e8e92..9c897af0a2f94 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/events.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml index f64dcbdb51cfd..47c6644b9f369 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/routes.xml b/app/code/Magento/AdminNotification/etc/adminhtml/routes.xml index 9ca1f2d4cd1ae..a710049993270 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/routes.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/system.xml b/app/code/Magento/AdminNotification/etc/adminhtml/system.xml index 8038b914fd65b..ab0cff5f897c1 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/system.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/config.xml b/app/code/Magento/AdminNotification/etc/config.xml index f1a1abb44546b..c088f7b5e11a7 100644 --- a/app/code/Magento/AdminNotification/etc/config.xml +++ b/app/code/Magento/AdminNotification/etc/config.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/di.xml b/app/code/Magento/AdminNotification/etc/di.xml index a5f94685ed9d1..03e415dd3714e 100644 --- a/app/code/Magento/AdminNotification/etc/di.xml +++ b/app/code/Magento/AdminNotification/etc/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/etc/module.xml b/app/code/Magento/AdminNotification/etc/module.xml index 77d9d4877ac0b..8fdfc713293d3 100644 --- a/app/code/Magento/AdminNotification/etc/module.xml +++ b/app/code/Magento/AdminNotification/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/registration.php b/app/code/Magento/AdminNotification/registration.php index ab3e6a6170db0..9bd6a540462f7 100644 --- a/app/code/Magento/AdminNotification/registration.php +++ b/app/code/Magento/AdminNotification/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_index.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_index.xml index 1d3650fa2afcb..1083e32f3c942 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_index.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml index 780e48378a3a4..4ac9f806e8c66 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/requirejs-config.js b/app/code/Magento/AdminNotification/view/adminhtml/requirejs-config.js index 69739a39dd4e3..c866e796e0ade 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -10,4 +10,4 @@ var config = { toolbarEntry: 'Magento_AdminNotification/toolbar_entry' } } -}; \ No newline at end of file +}; diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml index 260c7f9be9684..5e0e23246764a 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml @@ -1,6 +1,6 @@ getNoticeMessageText(); ?>
getReadDetailsText(); ?> - \ No newline at end of file + diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml index cd4a9a88ebe0c..a70a5e97b57c3 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js index aa5477ebafcf0..aedf996278834 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js index 1fbda8d0196ca..ae17af57fbef4 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js b/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js index e3d170ef09c57..2628174c1cd25 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js @@ -1,7 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + define([ 'jquery', 'mage/template', @@ -15,20 +16,22 @@ define([ modalClass: 'modal-system-messages', systemMessageTemplate: '<% _.each(data.items, function(item) { %>' + - '

  • ' + + '
  • ' + '<%= item.text %>' + '
  • ' + '<% }); %>' }, - _create: function() { + /** @inheritdoc */ + _create: function () { this.options.title = $('#message-system-all').attr('title'); this._super(); }, + /** @inheritdoc */ openModal: function (severity) { var superMethod = $.proxy(this._super, this); - //this.modal.options $.ajax({ url: this.options.ajaxUrl, @@ -56,6 +59,8 @@ define([ return this; }, + + /** @inheritdoc */ closeModal: function () { this._super(); } diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html index 869842e8ee87b..b6f659885e864 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html @@ -1,8 +1,8 @@
    \ No newline at end of file + html="$col.getLabel($row())"/> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html index 80f2f4f2423fb..bce380af785a4 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/toolbar_entry.js b/app/code/Magento/AdminNotification/view/adminhtml/web/toolbar_entry.js index 2d69f616f029a..ede614f116147 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/toolbar_entry.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/toolbar_entry.js @@ -1,17 +1,23 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + define([ - "jquery", - "jquery/ui", - "domReady!" + 'jquery', + 'jquery/ui', + 'domReady!' ], function ($) { 'use strict'; - // Mark notification as read via AJAX call + /** + * Mark notification as read via AJAX call. + * + * @param {String} notificationId + */ var markNotificationAsRead = function (notificationId) { var requestUrl = $('.notifications-wrapper .admin__action-dropdown-menu').attr('data-mark-as-read-url'); + $.ajax({ url: requestUrl, type: 'POST', @@ -22,19 +28,24 @@ define([ showLoader: false }); }, - notificationCount = $('.notifications-wrapper').attr('data-notification-count'), - // Remove notification from the list + /** + * Remove notification from the list. + * + * @param {jQuery} notificationEntry + */ removeNotificationFromList = function (notificationEntry) { + var notificationIcon, actionElement; + notificationEntry.remove(); notificationCount--; $('.notifications-wrapper').attr('data-notification-count', notificationCount); - if (notificationCount == 0) { + if (notificationCount == 0) {// eslint-disable-line eqeqeq // Change appearance of the bubble and its behavior when the last notification is removed $('.notifications-wrapper .admin__action-dropdown-menu').remove(); - var notificationIcon = $('.notifications-wrapper .notifications-icon'); + notificationIcon = $('.notifications-wrapper .notifications-icon'); notificationIcon.removeAttr('data-toggle'); notificationIcon.off('click.dropdown'); $('.notifications-action .notifications-counter').text('').hide(); @@ -45,12 +56,16 @@ define([ } $('.notifications-entry-last .notifications-counter').text(notificationCount); // Modify caption of the 'See All' link - var actionElement = $('.notifications-wrapper .admin__action-dropdown-menu .last .action-more'); + actionElement = $('.notifications-wrapper .admin__action-dropdown-menu .last .action-more'); actionElement.text(actionElement.text().replace(/\d+/, notificationCount)); } }, - // Show notification details + /** + * Show notification details. + * + * @param {jQuery} notificationEntry + */ showNotificationDetails = function (notificationEntry) { var notificationDescription = notificationEntry.find('.notifications-entry-description'), notificationDescriptionEnd = notificationEntry.find('.notifications-entry-description-end'); @@ -59,20 +74,22 @@ define([ notificationDescriptionEnd.addClass('_show'); } - if(notificationDescription.hasClass('_cutted')) { + if (notificationDescription.hasClass('_cutted')) { notificationDescription.removeClass('_cutted'); } }; // Show notification description when corresponding item is clicked - $('.notifications-wrapper .admin__action-dropdown-menu .notifications-entry').on('click.showNotification', function (event) { - // hide notification dropdown - $('.notifications-wrapper .notifications-icon').trigger('click.dropdown'); + $('.notifications-wrapper .admin__action-dropdown-menu .notifications-entry').on( + 'click.showNotification', + function (event) { + // hide notification dropdown + $('.notifications-wrapper .notifications-icon').trigger('click.dropdown'); - showNotificationDetails($(this)); - event.stopPropagation(); - - }); + showNotificationDetails($(this)); + event.stopPropagation(); + } + ); // Remove corresponding notification from the list and mark it as read $('.notifications-close').on('click.removeNotification', function (event) { @@ -83,19 +100,19 @@ define([ removeNotificationFromList(notificationEntry); // Checking for last unread notification to hide dropdown - if (notificationCount == 0) { + if (notificationCount == 0) {// eslint-disable-line eqeqeq $('.notifications-wrapper').removeClass('active') - .find('.notifications-action').removeAttr('data-toggle') - .off('click.dropdown'); + .find('.notifications-action') + .removeAttr('data-toggle') + .off('click.dropdown'); } event.stopPropagation(); }); // Hide notifications bubble - if (notificationCount == 0) { + if (notificationCount == 0) {// eslint-disable-line eqeqeq $('.notifications-action .notifications-counter').hide(); } else { $('.notifications-action .notifications-counter').show(); } - -}); \ No newline at end of file +}); diff --git a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php index ccbef52b5a818..d35a409b58b98 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -1,6 +1,6 @@ '', ImportAdvancedPricing::COL_TIER_PRICE_QTY => '', ImportAdvancedPricing::COL_TIER_PRICE => '', + ImportAdvancedPricing::COL_TIER_PRICE_TYPE => '' ]; /** @@ -279,6 +280,8 @@ protected function getExportData() } /** + * Correct export data. + * * @param array $exportData * @return array * @SuppressWarnings(PHPMD.UnusedLocalVariable) @@ -302,6 +305,12 @@ protected function correctExportData($exportData) : null ); unset($exportRow[ImportAdvancedPricing::VALUE_ALL_GROUPS]); + } elseif ($keyTemplate === ImportAdvancedPricing::COL_TIER_PRICE) { + $exportRow[$keyTemplate] = $row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + ? $row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + : $row[ImportAdvancedPricing::COL_TIER_PRICE]; + $exportRow[ImportAdvancedPricing::COL_TIER_PRICE_TYPE] + = $this->tierPriceTypeValue($row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE]); } else { $exportRow[$keyTemplate] = $row[$keyTemplate]; } @@ -311,11 +320,25 @@ protected function correctExportData($exportData) $customExportData[$key] = $exportRow; unset($exportRow); } + return $customExportData; } /** - * Get Tier and Group Pricing + * Check type for tier price. + * + * @param string $tierPricePercentage + * @return string + */ + private function tierPriceTypeValue($tierPricePercentage) + { + return $tierPricePercentage + ? ImportAdvancedPricing::TIER_PRICE_TYPE_PERCENT + : ImportAdvancedPricing::TIER_PRICE_TYPE_FIXED; + } + + /** + * Get tier prices. * * @param array $listSku * @param string $table @@ -336,6 +359,7 @@ protected function getTierPrices(array $listSku, $table) ImportAdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'ap.customer_group_id', ImportAdvancedPricing::COL_TIER_PRICE_QTY => 'ap.qty', ImportAdvancedPricing::COL_TIER_PRICE => 'ap.value', + ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE => 'ap.percentage_value', ]; if (isset($exportFilter) && !empty($exportFilter)) { $price = $exportFilter['tier_price']; @@ -371,6 +395,9 @@ protected function getTierPrices(array $listSku, $table) if (isset($price[1]) && !empty($price[1])) { $select->where('ap.value <= ?', $price[1]); } + if (isset($price[0]) && !empty($price[0]) || isset($price[1]) && !empty($price[1])) { + $select->orWhere('ap.percentage_value IS NOT NULL'); + } if (isset($updatedAtFrom) && !empty($updatedAtFrom)) { $select->where('cpe.updated_at >= ?', $updatedAtFrom); } diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index d751fdc75882d..0bb44c238c720 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -1,6 +1,6 @@ 'Tier Price data price or quantity value is invalid', ValidatorInterface::ERROR_INVALID_TIER_PRICE_SITE => 'Tier Price data website is invalid', ValidatorInterface::ERROR_INVALID_TIER_PRICE_GROUP => 'Tier Price customer group is invalid', + ValidatorInterface::ERROR_INVALID_TIER_PRICE_TYPE => 'Value for \'tier_price_value_type\' ' . + 'attribute contains incorrect value, acceptable values are Fixed, Discount', ValidatorInterface::ERROR_TIER_DATA_INCOMPLETE => 'Tier Price data is incomplete', ValidatorInterface::ERROR_INVALID_ATTRIBUTE_DECIMAL => 'Value for \'%s\' attribute contains incorrect value, acceptable values are in decimal format', @@ -70,7 +80,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract protected $needColumnCheck = true; /** - * Valid column names + * Valid column names. * * @array */ @@ -80,6 +90,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract self::COL_TIER_PRICE_CUSTOMER_GROUP, self::COL_TIER_PRICE_QTY, self::COL_TIER_PRICE, + self::COL_TIER_PRICE_TYPE ]; /** @@ -379,7 +390,10 @@ protected function saveAndReplaceAdvancedPrices() $rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] ), 'qty' => $rowData[self::COL_TIER_PRICE_QTY], - 'value' => $rowData[self::COL_TIER_PRICE], + 'value' => $rowData[self::COL_TIER_PRICE_TYPE] === self::TIER_PRICE_TYPE_FIXED + ? $rowData[self::COL_TIER_PRICE] : 0, + 'percentage_value' => $rowData[self::COL_TIER_PRICE_TYPE] === self::TIER_PRICE_TYPE_PERCENT + ? $rowData[self::COL_TIER_PRICE] : null, 'website_id' => $this->getWebsiteId($rowData[self::COL_TIER_PRICE_WEBSITE]) ]; } @@ -429,7 +443,7 @@ protected function saveProductPrices(array $priceData, $table) } } if ($priceIn) { - $this->_connection->insertOnDuplicate($tableName, $priceIn, ['value']); + $this->_connection->insertOnDuplicate($tableName, $priceIn, ['value', 'percentage_value']); } } return $this; @@ -542,19 +556,24 @@ protected function retrieveOldSkus() */ protected function processCountExistingPrices($prices, $table) { + $oldSkus = $this->retrieveOldSkus(); + $existProductIds = array_intersect_key($oldSkus, $prices); + if (!count($existProductIds)) { + return $this; + } + $tableName = $this->_resourceFactory->create()->getTable($table); $productEntityLinkField = $this->getProductEntityLinkField(); $existingPrices = $this->_connection->fetchAssoc( $this->_connection->select()->from( $tableName, ['value_id', $productEntityLinkField, 'all_groups', 'customer_group_id'] - ) + )->where($productEntityLinkField . ' IN (?)', $existProductIds) ); - $oldSkus = $this->retrieveOldSkus(); foreach ($existingPrices as $existingPrice) { - foreach ($oldSkus as $sku => $productId) { - if ($existingPrice[$productEntityLinkField] == $productId && isset($prices[$sku])) { - $this->incrementCounterUpdated($prices[$sku], $existingPrice); + foreach ($prices as $sku => $skuPrices) { + if (isset($oldSkus[$sku]) && $existingPrice[$productEntityLinkField] == $oldSkus[$sku]) { + $this->incrementCounterUpdated($skuPrices, $existingPrice); } } } diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php index 051628e7e4a92..ffc191ac655f7 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php @@ -1,6 +1,6 @@ hasEmptyColumns($value) ) { $this->_addMessages([self::ERROR_TIER_DATA_INCOMPLETE]); diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php new file mode 100644 index 0000000000000..d00d36b0eda27 --- /dev/null +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php @@ -0,0 +1,48 @@ +_addMessages([RowValidatorInterface::ERROR_INVALID_TIER_PRICE_TYPE]); + $isValid = false; + } + + return $isValid; + } +} diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php index 150555709b8b7..bc57b8482963a 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php @@ -1,6 +1,6 @@ tierPriceType = $objectManager->getObject( + AdvancedPricing\Validator\TierPriceType::class, + [] + ); + } + + /** + * Test for isValid() method. + * + * @dataProvider isValidDataProvider + * @param array $value + * @param bool $expectedResult + */ + public function testIsValid(array $value, $expectedResult) + { + $result = $this->tierPriceType->isValid($value); + $this->assertEquals($expectedResult, $result); + } + + /** + * Data Provider for testIsValid(). + * + * @return array + */ + public function isValidDataProvider() + { + return [ + [ + [AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_FIXED], + true + ], + [ + [AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_PERCENT], + true + ], + [ + [], + true + ], + [ + [AdvancedPricing::COL_TIER_PRICE_TYPE => null], + true + ], + [ + [AdvancedPricing::COL_TIER_PRICE_TYPE => 'wrong type'], + false + ] + ]; + } +} diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php index ad67ee5cc556a..6a0fd28ac7466 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php @@ -1,6 +1,6 @@ advancedPricing + $skuProduct = 'product1'; + $sku = $data[0][AdvancedPricing::COL_SKU]; + $advancedPricing = $this->getAdvancedPricingMock( + [ + 'retrieveOldSkus', + 'validateRow', + 'addRowError', + 'getCustomerGroupId', + 'getWebSiteId', + 'deleteProductTierPrices', + 'getBehavior', + 'saveAndReplaceAdvancedPrices', + 'processCountExistingPrices', + 'processCountNewPrices' + ] + ); + $advancedPricing ->expects($this->any()) ->method('getBehavior') ->willReturn(\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND); $this->dataSourceModel->expects($this->at(0))->method('getNextBunch')->willReturn($data); - $this->advancedPricing->expects($this->any())->method('validateRow')->willReturn(true); + $advancedPricing->expects($this->any())->method('validateRow')->willReturn(true); - $this->advancedPricing->expects($this->any())->method('getCustomerGroupId')->willReturnMap( + $advancedPricing->expects($this->any())->method('getCustomerGroupId')->willReturnMap( [ [$data[0][AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP], $tierCustomerGroupId], ] ); - $this->advancedPricing->expects($this->any())->method('getWebSiteId')->willReturnMap( + $advancedPricing->expects($this->any())->method('getWebSiteId')->willReturnMap( [ [$data[0][AdvancedPricing::COL_TIER_PRICE_WEBSITE], $tierWebsiteId], ] ); - $this->advancedPricing->expects($this->any())->method('saveProductPrices')->will($this->returnSelf()); + $oldSkus = [$sku => $skuProduct]; + $expectedTierPrices[$sku][0][self::LINK_FIELD] = $skuProduct; + $advancedPricing->expects($this->once())->method('retrieveOldSkus')->willReturn($oldSkus); + $this->connection->expects($this->once()) + ->method('insertOnDuplicate') + ->with(self::TABLE_NAME, $expectedTierPrices[$sku], ['value', 'percentage_value']); - $this->advancedPricing->expects($this->any())->method('processCountExistingPrices')->willReturnSelf(); - $this->advancedPricing->expects($this->any())->method('processCountNewPrices')->willReturnSelf(); + $advancedPricing->expects($this->any())->method('processCountExistingPrices')->willReturnSelf(); + $advancedPricing->expects($this->any())->method('processCountNewPrices')->willReturnSelf(); - $result = $this->invokeMethod($this->advancedPricing, 'saveAndReplaceAdvancedPrices'); + $result = $this->invokeMethod($advancedPricing, 'saveAndReplaceAdvancedPrices'); - $this->assertEquals($this->advancedPricing, $result); + $this->assertEquals($advancedPricing, $result); + } + + /** + * Test method saveAndReplaceAdvancedPrices with append import behaviour. + */ + public function testSaveAndReplaceAdvancedPricesAppendBehaviourDataAndCallsWithoutTierPrice() + { + $data = [ + 0 => [ + AdvancedPricing::COL_SKU => 'sku value', + AdvancedPricing::COL_TIER_PRICE_WEBSITE => null, + AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups', + AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', + AdvancedPricing::COL_TIER_PRICE => 'tier price value', + AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_FIXED + ], + ]; + $tierCustomerGroupId = 'tier customer group id value'; + $tierWebsiteId = 'tier website id value'; + $expectedTierPrices = []; + + $skuProduct = 'product1'; + $sku = $data[0][AdvancedPricing::COL_SKU]; + $advancedPricing = $this->getAdvancedPricingMock( + [ + 'retrieveOldSkus', + 'validateRow', + 'addRowError', + 'getCustomerGroupId', + 'getWebSiteId', + 'deleteProductTierPrices', + 'getBehavior', + 'saveAndReplaceAdvancedPrices', + 'processCountExistingPrices', + 'processCountNewPrices' + ] + ); + $advancedPricing + ->expects($this->any()) + ->method('getBehavior') + ->willReturn(\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND); + $this->dataSourceModel->expects($this->at(0))->method('getNextBunch')->willReturn($data); + $advancedPricing->expects($this->any())->method('validateRow')->willReturn(true); + + $advancedPricing->expects($this->any())->method('getCustomerGroupId')->willReturnMap( + [ + [$data[0][AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP], $tierCustomerGroupId], + ] + ); + + $advancedPricing->expects($this->any())->method('getWebSiteId')->willReturnMap( + [ + [$data[0][AdvancedPricing::COL_TIER_PRICE_WEBSITE], $tierWebsiteId], + ] + ); + + $oldSkus = [$sku => $skuProduct]; + $expectedTierPrices[$sku][0][self::LINK_FIELD] = $skuProduct; + $advancedPricing->expects($this->never())->method('retrieveOldSkus')->willReturn($oldSkus); + $this->connection->expects($this->never()) + ->method('insertOnDuplicate') + ->with(self::TABLE_NAME, $expectedTierPrices[$sku], ['value', 'percentage_value']); + + $advancedPricing->expects($this->any())->method('processCountExistingPrices')->willReturnSelf(); + $advancedPricing->expects($this->any())->method('processCountNewPrices')->willReturnSelf(); + + $result = $this->invokeMethod($advancedPricing, 'saveAndReplaceAdvancedPrices'); + + $this->assertEquals($advancedPricing, $result); } /** @@ -575,6 +665,7 @@ public function saveAndReplaceAdvancedPricesAppendBehaviourDataProvider() AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups ', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', + AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_FIXED ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -585,25 +676,25 @@ public function saveAndReplaceAdvancedPricesAppendBehaviourDataProvider() 'sku value' => [ [ 'all_groups' => false, - //$rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS 'customer_group_id' => 'tier customer group id value', - //$tierCustomerGroupId 'qty' => 'tier price qty value', 'value' => 'tier price value', 'website_id' => 'tier website id value', + 'percentage_value' => null ], ], ], ], - [// tier customer group is equal to all group + [ '$data' => [ 0 => [ AdvancedPricing::COL_SKU => 'sku value', //tier AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier price website value', - AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => AdvancedPricing::VALUE_ALL_GROUPS, + AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups ', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', + AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_PERCENT ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -613,33 +704,44 @@ public function saveAndReplaceAdvancedPricesAppendBehaviourDataProvider() '$expectedTierPrices' => [ 'sku value' => [ [ - 'all_groups' => true, - //$rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS + 'all_groups' => false, 'customer_group_id' => 'tier customer group id value', - //$tierCustomerGroupId 'qty' => 'tier price qty value', - 'value' => 'tier price value', + 'value' => 0, + 'percentage_value' => 'tier price value', 'website_id' => 'tier website id value', ], ], ], ], - [ + [// tier customer group is equal to all group '$data' => [ 0 => [ AdvancedPricing::COL_SKU => 'sku value', //tier - AdvancedPricing::COL_TIER_PRICE_WEBSITE => null, - AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups', + AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier price website value', + AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => AdvancedPricing::VALUE_ALL_GROUPS, AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', + AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_FIXED ], ], '$tierCustomerGroupId' => 'tier customer group id value', '$groupCustomerGroupId' => 'group customer group id value', '$tierWebsiteId' => 'tier website id value', '$groupWebsiteId' => 'group website id value', - '$expectedTierPrices' => [], + '$expectedTierPrices' => [ + 'sku value' => [ + [ + 'all_groups' => true, + 'customer_group_id' => 'tier customer group id value', + 'qty' => 'tier price qty value', + 'value' => 'tier price value', + 'website_id' => 'tier website id value', + 'percentage_value' => null + ], + ], + ], ], [ '$data' => [ @@ -650,6 +752,7 @@ public function saveAndReplaceAdvancedPricesAppendBehaviourDataProvider() AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', + AdvancedPricing::COL_TIER_PRICE_TYPE => AdvancedPricing::TIER_PRICE_TYPE_FIXED ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -660,12 +763,11 @@ public function saveAndReplaceAdvancedPricesAppendBehaviourDataProvider() 'sku value' => [ [ 'all_groups' => false, - //$rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS 'customer_group_id' => 'tier customer group id value', - //$tierCustomerGroupId 'qty' => 'tier price qty value', 'value' => 'tier price value', 'website_id' => 'tier website id value', + 'percentage_value' => null ], ] ], @@ -746,7 +848,7 @@ public function testSaveProductPrices($priceData, $oldSkus, $priceIn, $callNum) $this->connection->expects($this->exactly($callNum)) ->method('insertOnDuplicate') - ->with(self::TABLE_NAME, $priceIn, ['value']); + ->with(self::TABLE_NAME, $priceIn, ['value', 'percentage_value']); $this->invokeMethod($this->advancedPricing, 'saveProductPrices', [$priceData, 'table']); } diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Indexer/Product/Price/Plugin/ImportTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Indexer/Product/Price/Plugin/ImportTest.php index d65271d9ace54..67f6d33a9df15 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Indexer/Product/Price/Plugin/ImportTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Indexer/Product/Price/Plugin/ImportTest.php @@ -1,6 +1,6 @@ @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/di.xml b/app/code/Magento/AdvancedPricingImportExport/etc/di.xml index c3444069c14c0..950054ab52412 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/di.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/di.xml @@ -1,7 +1,7 @@ @@ -14,6 +14,7 @@ Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\TierPrice Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\Website + Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\TierPriceType diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/export.xml b/app/code/Magento/AdvancedPricingImportExport/etc/export.xml index f04e399fdfedb..da176e7bd73e1 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/export.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/export.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/import.xml b/app/code/Magento/AdvancedPricingImportExport/etc/import.xml index 711a7bf270ce6..80c8873aad387 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/import.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/import.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/module.xml b/app/code/Magento/AdvancedPricingImportExport/etc/module.xml index 4ae0d6516e66f..f9ad9f34b2ad6 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/module.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/AdvancedPricingImportExport/registration.php b/app/code/Magento/AdvancedPricingImportExport/registration.php index 86dcff3d60a68..a9477f3a9a1fb 100644 --- a/app/code/Magento/AdvancedPricingImportExport/registration.php +++ b/app/code/Magento/AdvancedPricingImportExport/registration.php @@ -1,6 +1,6 @@ subscriptionStatusProvider = $labelStatusProvider; + } + + /** + * Unset some non-related element parameters + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + $element->setData( + 'value', + $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/Controller/Adminhtml/BasicTier/SignUp.php b/app/code/Magento/Analytics/Controller/Adminhtml/BasicTier/SignUp.php new file mode 100644 index 0000000000000..73c39d7c14a59 --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/BasicTier/SignUp.php @@ -0,0 +1,62 @@ +config = $config; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::report_basic_tier'); + } + + /** + * Provides link to Basic Tier signup + * + * @return \Magento\Framework\Controller\AbstractResult + */ + public function execute() + { + return $this->resultRedirectFactory->create()->setUrl( + $this->config->getConfigDataValue($this->basicTierUrlPath) + ); + } +} 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 0000000000000..ac9d93a579ffd --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php @@ -0,0 +1,71 @@ +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 (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/Activate.php b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Activate.php new file mode 100644 index 0000000000000..56cc44bbc95dd --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Activate.php @@ -0,0 +1,118 @@ +subscription = $subscription; + $this->logger = $logger; + $this->notificationTime = $notificationTime; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Activate subscription to Magento Analytics via AJAX. + * + * @return Json + */ + public function execute() + { + try { + if ($this->getRequest()->getParam($this->subscriptionApprovedField)) { + $this->subscription->enable(); + } else { + $this->notificationTime->unsetLastTimeNotificationValue(); + } + $responseContent = [ + 'success' => true, + 'error_message' => '', + ]; + } catch (LocalizedException $e) { + $responseContent = [ + 'success' => false, + 'error_message' => $e->getMessage(), + ]; + $this->logger->error($e->getMessage()); + } catch (\Exception $e) { + $responseContent = [ + 'success' => false, + 'error_message' => __( + 'Sorry, there was an error processing your registration request to Magento Analytics. ' + . 'Please try again later.' + ), + ]; + $this->logger->error($e->getMessage()); + } + /** @var Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + return $resultJson->setData($responseContent); + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Postpone.php b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Postpone.php new file mode 100644 index 0000000000000..b87730132e3d4 --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Postpone.php @@ -0,0 +1,101 @@ +dateTimeFactory = $dateTimeFactory; + $this->notificationTime = $notificationTime; + $this->logger = $logger; + parent::__construct($context); + + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Postpones notification about subscription + * + * @return Json + */ + public function execute() + { + try { + $dateTime = $this->dateTimeFactory->create(); + $responseContent = [ + 'success' => $this->notificationTime->storeLastTimeNotification($dateTime->getTimestamp()), + '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' => __('Error occurred during postponement notification') + ]; + } + /** @var Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + return $resultJson->setData($responseContent); + } +} diff --git a/app/code/Magento/Analytics/Cron/CollectData.php b/app/code/Magento/Analytics/Cron/CollectData.php new file mode 100644 index 0000000000000..5b2abd2fbe6a9 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/CollectData.php @@ -0,0 +1,53 @@ +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 0000000000000..b8d79ae2707b3 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/SignUp.php @@ -0,0 +1,131 @@ +connector = $connector; + $this->configWriter = $configWriter; + $this->inboxFactory = $inboxFactory; + $this->inboxResource = $inboxResource; + $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) { + $this->deleteAnalyticsCronExpr(); + return false; + } + + if ($attemptsCount <= 0) { + $this->deleteAnalyticsCronExpr(); + $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $inboxNotification = $this->inboxFactory->create(); + $inboxNotification->addNotice( + "Analytics subscription unsuccessful", + "Analytics subscription unsuccessful" + ); + $this->inboxResource->save($inboxNotification); + 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 0000000000000..4ed1346ade9a5 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/Update.php @@ -0,0 +1,78 @@ +connector = $connector; + $this->configWriter = $configWriter; + $this->reinitableConfig = $reinitableConfig; + $this->flagManager = $flagManager; + } + + /** + * Execute scheduled update operation + * + * @return bool + */ + public function execute() + { + $updateResult = $this->connector->execute('update'); + if ($updateResult === false) { + return false; + } + $this->configWriter->delete(BaseUrlConfigPlugin::UPDATE_CRON_STRING_PATH); + $this->flagManager->deleteFlag(BaseUrlConfigPlugin::OLD_BASE_URL_FLAG_CODE); + $this->reinitableConfig->reinit(); + return true; + } +} diff --git a/app/code/Magento/Analytics/LICENSE.txt b/app/code/Magento/Analytics/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /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 " 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 0000000000000..f39d641b18a19 --- /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 " 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 0000000000000..abef37f3a62d3 --- /dev/null +++ b/app/code/Magento/Analytics/Model/AnalyticsToken.php @@ -0,0 +1,92 @@ +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/Condition/CanViewNotification.php b/app/code/Magento/Analytics/Model/Condition/CanViewNotification.php new file mode 100644 index 0000000000000..a7dc93bcab800 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Condition/CanViewNotification.php @@ -0,0 +1,68 @@ +notificationTime = $notificationTime; + $this->dateTimeFactory = $dateTimeFactory; + } + + /** + * Validate is notification popup can be shown + * + * @return bool + */ + public function validate() + { + $lastNotificationTime = $this->notificationTime->getLastTimeNotification(); + if (!$lastNotificationTime) { + return false; + } + $datetime = $this->dateTimeFactory->create(); + return ( + $datetime->getTimestamp() >= $lastNotificationTime + $this->notificationInterval + ); + } +} diff --git a/app/code/Magento/Analytics/Model/Config.php b/app/code/Magento/Analytics/Model/Config.php new file mode 100644 index 0000000000000..a4601ec4430c4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config.php @@ -0,0 +1,40 @@ +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/CollectionTime.php b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php new file mode 100644 index 0000000000000..208f3a5dad7c8 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php @@ -0,0 +1,91 @@ +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('#(?\d{2}),(?\d{2}),(?\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 0000000000000..8cbb64fb26337 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php @@ -0,0 +1,71 @@ +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 { + $this->subscriptionHandler->process($this); + } 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 0000000000000..0f169a780dcb6 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php @@ -0,0 +1,174 @@ +configWriter = $configWriter; + $this->flagManager = $flagManager; + $this->analyticsToken = $analyticsToken; + $this->notificationTime = $notificationTime; + } + + /** + * Performs change subscription environment on config value change. + * + * Activate process of subscription handling + * if subscription was activated and Analytics token has not been received + * or interrupt subscription handling. + * + * @param Value $configValue + * + * @return bool + */ + public function process(Value $configValue) + { + if (!$configValue->isValueChanged()) { + return true; + } + + $enabled = $configValue->getData('value'); + + if (!$enabled) { + $this->disableCollectionData(); + } + + if (!$this->analyticsToken->isTokenExist()) { + if ($enabled) { + $this->setCronSchedule(); + $this->setAttemptsFlag(); + $this->notificationTime->unsetLastTimeNotificationValue(); + } else { + $this->unsetAttemptsFlag(); + } + } + + return true; + } + + /** + * Set cron schedule setting into config for activation of subscription process. + * + * @return bool + */ + private function setCronSchedule() + { + $cronExprArray = [ + '0', # Minute + '*', # Hour + '*', # Day of the Month + '*', # Month of the Year + '*', # Day of the Week + ]; + + $cronExprString = join(' ', $cronExprArray); + + $this->configWriter->save(self::CRON_STRING_PATH, $cronExprString); + + 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); + } + + /** + * 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 0000000000000..c9759855e843f --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php @@ -0,0 +1,32 @@ +getValue())) { + throw new LocalizedException(__('Please select a vertical.')); + } + + return $this; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Data.php b/app/code/Magento/Analytics/Model/Config/Data.php new file mode 100644 index 0000000000000..addc305b71a9e --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Data.php @@ -0,0 +1,29 @@ + [ + * '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 0000000000000..e76cc57ee37d8 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Reader.php @@ -0,0 +1,52 @@ +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/Reader/Xml.php b/app/code/Magento/Analytics/Model/Config/Reader/Xml.php new file mode 100644 index 0000000000000..4a333f00324f9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Reader/Xml.php @@ -0,0 +1,61 @@ + 'name', + ]; + + /** + * @param FileResolverInterface $fileResolver + * @param ConverterInterface $converter + * @param SchemaLocatorInterface $schemaLocator + * @param ValidationStateInterface $validationState + * @param string $fileName + * @param array $idAttributes + * @param string $domDocumentClass + * @param string $defaultScope + */ + public function __construct( + FileResolverInterface $fileResolver, + ConverterInterface $converter, + SchemaLocatorInterface $schemaLocator, + ValidationStateInterface $validationState, + $fileName = 'analytics.xml', + $idAttributes = [], + $domDocumentClass = Dom::class, + $defaultScope = 'global' + ) { + parent::__construct( + $fileResolver, + $converter, + $schemaLocator, + $validationState, + $fileName, + $idAttributes, + $domDocumentClass, + $defaultScope + ); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/SchemaLocator.php b/app/code/Magento/Analytics/Model/Config/SchemaLocator.php new file mode 100644 index 0000000000000..1785174f4da65 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/SchemaLocator.php @@ -0,0 +1,56 @@ +urnResolver = $urnResolver; + } + + /** + * Get path to merged config schema. + * + * @return string|null + */ + public function getSchema() + { + return $this->urnResolver->getRealPath($this->schema); + } + + /** + * Get path to pre file validation schema. + * + * @return string|null + */ + public function getPerFileSchema() + { + return null; + } +} 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 0000000000000..6bc00465b0607 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Source/Vertical.php @@ -0,0 +1,51 @@ +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 0000000000000..be719e5640f1a --- /dev/null +++ b/app/code/Magento/Analytics/Model/ConfigInterface.php @@ -0,0 +1,22 @@ + '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 0000000000000..8ac1a5fce4371 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/CommandInterface.php @@ -0,0 +1,20 @@ +curlFactory = $curlFactory; + $this->responseFactory = $responseFactory; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function request($method, $url, $body = '', array $headers = [], $version = '1.1') + { + $curl = $this->curlFactory->create(); + + $curl->write($method, $url, $version, $headers, $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 false; + } + + return $this->responseFactory->create($result); + } +} 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 0000000000000..37eb6865544a0 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php @@ -0,0 +1,29 @@ +analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $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() + { + $otp = false; + + if ($this->analyticsToken->isTokenExist()) { + try { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->otpUrlConfigPath), + $this->getRequestJson(), + ['Content-Type: application/json'] + ); + + if ($response) { + $otp = $this->extractOtp($response); + + if (!$otp) { + $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.' + ) + ); + } + } + } catch (\Exception $e) { + $this->logger->critical($e); + } + } + + return $otp; + } + + /** + * Prepares request data in JSON format. + * + * @return string + */ + private function getRequestJson() + { + return json_encode( + [ + "access-token" => $this->analyticsToken->getToken(), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + } + + /** + * Extracts an OTP from the response. + * + * Returns the OTP or FALSE if the OTP is not found. + * + * @param HttpResponse $response + * @return string|false + */ + private function extractOtp(HttpResponse $response) + { + $otp = false; + + if ($response->getStatus() === 201) { + $body = json_decode($response->getBody(), 1); + + $otp = !empty($body['otp']) ? $body['otp'] : false; + } + + return $otp; + } +} 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 0000000000000..f9fd78420be46 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php @@ -0,0 +1,74 @@ +analyticsToken = $analyticsToken; + $this->integrationManager = $integrationManager; + $this->signUpRequest = $signUpRequest; + } + + /** + * 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() + { + $integrationToken = $this->integrationManager->generateToken(); + if ($integrationToken) { + $this->integrationManager->activateIntegration(); + $responseToken = $this->signUpRequest->call($integrationToken->getToken()); + if ($responseToken) { + $this->analyticsToken->storeToken($responseToken); + } + } + return ((bool)$integrationToken && isset($responseToken) && (bool)$responseToken); + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/SignUpRequest.php b/app/code/Magento/Analytics/Model/Connector/SignUpRequest.php new file mode 100644 index 0000000000000..74bfa84d0ef62 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/SignUpRequest.php @@ -0,0 +1,135 @@ +config = $config; + $this->httpClient = $httpClient; + $this->logger = $logger; + } + + /** + * Prepares request data in JSON format. + * + * @param string $integrationToken + * @return string + */ + private function getRequestJson($integrationToken) + { + return json_encode( + [ + "token" => $integrationToken, + "url" => $this->config->getConfigDataValue( + Store::XML_PATH_SECURE_BASE_URL + ) + ] + ); + } + + /** + * Extracts an MBI access token from the response. + * + * Returns the token or FALSE if the token is not found. + * + * @param HttpResponse $response + * @return string|false + */ + private function extractAccessToken(HttpResponse $response) + { + $token = false; + + if ($response->getStatus() === 201) { + $body = json_decode($response->getBody(), 1); + + if (isset($body['access-token']) && !empty($body['access-token'])) { + $token = $body['access-token']; + } + } + + return $token; + } + + /** + * Performs a 'signUp' call to MBI service. + * + * Returns MBI access token or FALSE in case of failure. + * + * @param string $integrationToken + * @return string|false + */ + public function call($integrationToken) + { + $token = false; + + try { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getConfigDataValue($this->signUpUrlPath), + $this->getRequestJson($integrationToken), + ['Content-Type: application/json'] + ); + + if ($response) { + $token = $this->extractAccessToken($response); + + if (!$token) { + $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.' + ) + ); + } + } + } catch (\Exception $e) { + $this->logger->critical($e); + } + + return $token; + } +} 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 0000000000000..ba9e779122f55 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php @@ -0,0 +1,124 @@ +analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->logger = $logger; + $this->flagManager = $flagManager; + } + + /** + * Executes update request to MBI api in case store url was changed + * @return bool + */ + public function execute() + { + $result = false; + try { + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::PUT, + $this->config->getConfigDataValue($this->updateUrlPath), + $this->getRequestJson(), + ['Content-Type: application/json'] + ); + + if ($response) { + $result = $response->getStatus() === 201; + 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.' + ) + ); + } + } + } + } catch (\Exception $e) { + $this->logger->critical($e); + } + + return $result; + } + + /** + * Prepares request data in JSON format. + * @return string + */ + private function getRequestJson() + { + return json_encode( + [ + "url" => $this->flagManager->getFlagData(BaseUrlConfigPlugin::OLD_BASE_URL_FLAG_CODE), + "new-url" => $this->config->getConfigDataValue( + Store::XML_PATH_SECURE_BASE_URL + ), + "access-token" => $this->analyticsToken->getToken(), + ] + ); + } +} diff --git a/app/code/Magento/Analytics/Model/Cryptographer.php b/app/code/Magento/Analytics/Model/Cryptographer.php new file mode 100644 index 0000000000000..b3b691399994d --- /dev/null +++ b/app/code/Magento/Analytics/Model/Cryptographer.php @@ -0,0 +1,130 @@ +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 0000000000000..358765ea79347 --- /dev/null +++ b/app/code/Magento/Analytics/Model/EncodedContext.php @@ -0,0 +1,52 @@ +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/ExportDataHandler.php b/app/code/Magento/Analytics/Model/ExportDataHandler.php new file mode 100644 index 0000000000000..3f451e8a07872 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ExportDataHandler.php @@ -0,0 +1,205 @@ +filesystem = $filesystem; + $this->archive = $archive; + $this->reportWriter = $reportWriter; + $this->cryptographer = $cryptographer; + $this->fileRecorder = $fileRecorder; + } + + /** + * Execute collecting new data for MBI. + * + * @return bool + */ + 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/FileInfo.php b/app/code/Magento/Analytics/Model/FileInfo.php new file mode 100644 index 0000000000000..b7c38709944d3 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfo.php @@ -0,0 +1,52 @@ +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 0000000000000..a55df9070a0d0 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfoManager.php @@ -0,0 +1,122 @@ +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 0000000000000..c1ef8da247147 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileRecorder.php @@ -0,0 +1,137 @@ +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/FlagManager.php b/app/code/Magento/Analytics/Model/FlagManager.php new file mode 100644 index 0000000000000..47c8f0a0da2dd --- /dev/null +++ b/app/code/Magento/Analytics/Model/FlagManager.php @@ -0,0 +1,93 @@ +flagFactory = $flagFactory; + $this->flagResource = $flagResource; + } + + /** + * Return raw data from flag + * @param string $flagCode + * @return mixed + */ + public function getFlagData($flagCode) + { + return $this->getFlagObject($flagCode)->getFlagData(); + } + + /** + * Save flag by code + * @param string $flagCode + * @param mixed $value + * @return bool + */ + public function saveFlag($flagCode, $value) + { + $flag = $this->getFlagObject($flagCode); + $flag->setFlagData($value); + $this->flagResource->save($flag); + return true; + } + + /** + * Delete flag by code + * + * @param string $flagCode + * @return bool + */ + public function deleteFlag($flagCode) + { + $flag = $this->getFlagObject($flagCode); + if ($flag->getId()) { + $this->flagResource->delete($flag); + } + return true; + } + + /** + * Returns flag object + * + * @param string $flagCode + * @return Flag + */ + private function getFlagObject($flagCode) + { + /** @var Flag $flag */ + $flag = $this->flagFactory + ->create(['data' => ['flag_code' => $flagCode]]); + $this->flagResource->load($flag, $flagCode, 'flag_code'); + return $flag; + } +} diff --git a/app/code/Magento/Analytics/Model/IntegrationManager.php b/app/code/Magento/Analytics/Model/IntegrationManager.php new file mode 100644 index 0000000000000..bf625f03dc69a --- /dev/null +++ b/app/code/Magento/Analytics/Model/IntegrationManager.php @@ -0,0 +1,126 @@ +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|string + */ + 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 0000000000000..cbbbca93d6c47 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Link.php @@ -0,0 +1,60 @@ +url; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } + + /** + * @param string $url + * @return void + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @param string $initializationVector + * @return void + */ + public function setInitializationVector($initializationVector) + { + $this->initializationVector = $initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/LinkProvider.php b/app/code/Magento/Analytics/Model/LinkProvider.php new file mode 100644 index 0000000000000..2d4e7dad04073 --- /dev/null +++ b/app/code/Magento/Analytics/Model/LinkProvider.php @@ -0,0 +1,67 @@ +linkInterfaceFactory = $linkInterfaceFactory; + $this->fileInfoManager = $fileInfoManager; + $this->storeManager = $storeManager; + } + + /** + * @inheritdoc + */ + public function get() + { + $fileInfo = $this->fileInfoManager->load(); + if ($fileInfo->getPath() === null || $fileInfo->getInitializationVector() === null) { + throw new Exception(__('File is not ready yet.'), 0, Exception::HTTP_NOT_FOUND); + } + $link = $this->linkInterfaceFactory->create(); + $link->setUrl( + $this->storeManager->getStore()->getBaseUrl( + UrlInterface::URL_TYPE_MEDIA + ) . $fileInfo->getPath() + ); + $link->setInitializationVector(base64_encode($fileInfo->getInitializationVector())); + return $link; + } +} diff --git a/app/code/Magento/Analytics/Model/NotificationTime.php b/app/code/Magento/Analytics/Model/NotificationTime.php new file mode 100644 index 0000000000000..4df4cc970ee6d --- /dev/null +++ b/app/code/Magento/Analytics/Model/NotificationTime.php @@ -0,0 +1,65 @@ +flagManager = $flagManager; + } + + /** + * Stores last notification time + * + * @param string $value + * @return bool + */ + public function storeLastTimeNotification($value) + { + return $this->flagManager->saveFlag(self::NOTIFICATION_TIME, $value); + } + + /** + * Returns last time when merchant was notified about Analytic services + * + * @return int + */ + public function getLastTimeNotification() + { + return $this->flagManager->getFlagData(self::NOTIFICATION_TIME); + } + + /** + * Remove last notification time flag. + * + * @return bool + */ + public function unsetLastTimeNotificationValue() + { + return $this->flagManager->deleteFlag(self::NOTIFICATION_TIME); + } +} 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 0000000000000..e4d4441edbbf4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php @@ -0,0 +1,101 @@ +flagManager = $flagManager; + $this->subscriptionStatusProvider = $subscriptionStatusProvider; + $this->configWriter = $configWriter; + } + + /** + * Sets update analytics cron job if base url was changed. + * + * @param \Magento\Config\Model\Config\Backend\Baseurl $subject + * @param \Magento\Config\Model\Config\Backend\Baseurl $result + * @return \Magento\Config\Model\Config\Backend\Baseurl + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterSave( + \Magento\Config\Model\Config\Backend\Baseurl $subject, + \Magento\Config\Model\Config\Backend\Baseurl $result + ) { + if (!$result->isValueChanged()) { + return $result; + } + + if ($this->isPluginApplicable($result)) { + $this->flagManager->saveFlag(static::OLD_BASE_URL_FLAG_CODE, $result->getOldValue()); + $this->configWriter->save(self::UPDATE_CRON_STRING_PATH, $this->cronExpression); + } + + return $result; + } + + /** + * @param \Magento\Config\Model\Config\Backend\Baseurl $result + * + * @return bool + */ + private function isPluginApplicable(\Magento\Config\Model\Config\Backend\Baseurl $result) + { + return $result->getData('path') === \Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL + && $this->subscriptionStatusProvider->getStatus() === SubscriptionStatusProvider::ENABLED; + } +} diff --git a/app/code/Magento/Analytics/Model/ProviderFactory.php b/app/code/Magento/Analytics/Model/ProviderFactory.php new file mode 100644 index 0000000000000..b146c79ccb0b7 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ProviderFactory.php @@ -0,0 +1,39 @@ +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 0000000000000..e408106796dc4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportUrlProvider.php @@ -0,0 +1,75 @@ +analyticsToken = $analyticsToken; + $this->otpRequest = $otpRequest; + $this->config = $config; + } + + /** + * Provide URL on resource with reports. + * + * @return string + */ + public function getUrl() + { + $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 0000000000000..80b1210f9677a --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriter.php @@ -0,0 +1,102 @@ +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 0000000000000..20a8e5db4ffb8 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriterInterface.php @@ -0,0 +1,28 @@ +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 0000000000000..314225dc3d346 --- /dev/null +++ b/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php @@ -0,0 +1,102 @@ +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/Subscription.php b/app/code/Magento/Analytics/Model/Subscription.php new file mode 100644 index 0000000000000..e59fbcb1c3d70 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Subscription.php @@ -0,0 +1,108 @@ +configValueFactory = $configValueFactory; + $this->configStructure = $configStructure; + $this->configValueResource = $configValueResource; + $this->reinitableConfig = $reinitableConfig; + } + + /** + * Set subscription enabled config value. + * + * @return boolean + */ + public function enable() + { + /** @var Field $field */ + $field = $this->configStructure->getElement($this->enabledConfigStructurePath); + /** @var Value $configValue */ + $configValue = $field->hasBackendModel() + ? $field->getBackendModel() + : $this->configValueFactory->create(); + $configPath = $field->getConfigPath() ?: $this->enabledConfigStructurePath; + + $this->configValueResource + ->load($configValue, $configPath, 'path'); + + $configValue->setValue($this->yesValueDropdown); + $configValue->setPath($configPath); + + $this->configValueResource + ->save($configValue); + + $this->reinitableConfig->reinit(); + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php new file mode 100644 index 0000000000000..80dd2f7cc1bee --- /dev/null +++ b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php @@ -0,0 +1,109 @@ +systemConfig = $systemConfig; + $this->analyticsToken = $analyticsToken; + } + + /** + * 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. + * + * @return string + */ + public function getStatus() + { + $checkboxState = $this->systemConfig->get('default/analytics/subscription/enabled'); + return $this->resolveStatus($checkboxState); + } + + /** + * Resolves subscription status depending on + * subscription config value (enabled, disabled). + * + * @param bool $isSubscriptionEnabled + * + * @return string + */ + private function resolveStatus($isSubscriptionEnabled) + { + if (!$isSubscriptionEnabled) { + return static::DISABLED; + } + + $status = static::PENDING; + + if ($this->analyticsToken->isTokenExist()) { + $status = static::ENABLED; + } + + return $status; + } + + /** + * Retrieve status for subscription that enabled in config. + * + * @return string + */ + public function getStatusForEnabledSubscription() + { + return $this->resolveStatus(true); + } + + /** + * Retrieve status for subscription that disabled in config. + * + * @return string + */ + public function getStatusForDisabledSubscription() + { + return $this->resolveStatus(false); + } +} diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md new file mode 100644 index 0000000000000..cb8fdc3287190 --- /dev/null +++ b/app/code/Magento/Analytics/README.md @@ -0,0 +1,12 @@ +## Overview + +The Magento_Analytics module provides integration with +[Magento Business Intelligence](https://magento.com/products/business-intelligence). Admin user can subscribe to +Advanced Analytics, navigate to Advanced Analytics reports and Magento Business Intelligence subscription page. + +The Magento_Analytics module adds: + +- Advanced Analytics subscription pop-up +- backend menu link to Magento Business Intelligence subscription page under Reports menu +- Magento Analytics configuration page in Stores-Configuration->General menu +- configuration to build a data collection for BI system \ No newline at end of file diff --git a/app/code/Magento/Analytics/ReportXml/Config.php b/app/code/Magento/Analytics/ReportXml/Config.php new file mode 100644 index 0000000000000..53d4cee09191d --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config.php @@ -0,0 +1,43 @@ +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 0000000000000..c0152ed262c52 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php @@ -0,0 +1,61 @@ +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/Data.php b/app/code/Magento/Analytics/ReportXml/Config/Data.php new file mode 100644 index 0000000000000..1d3f69dc30373 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Data.php @@ -0,0 +1,29 @@ +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/Config/Reader/Xml.php b/app/code/Magento/Analytics/ReportXml/Config/Reader/Xml.php new file mode 100644 index 0000000000000..2ff333cbb81ef --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Reader/Xml.php @@ -0,0 +1,56 @@ + 'name' + ]; + + /** + * @param \Magento\Framework\Config\FileResolverInterface $fileResolver + * @param \Magento\Analytics\ReportXml\Config\Converter\Xml $converter + * @param \Magento\Analytics\ReportXml\Config\SchemaLocator $schemaLocator + * @param \Magento\Framework\Config\ValidationStateInterface $validationState + * @param string $fileName + * @param array $idAttributes + * @param string $domDocumentClass + * @param string $defaultScope + */ + public function __construct( + \Magento\Framework\Config\FileResolverInterface $fileResolver, + \Magento\Analytics\ReportXml\Config\Converter\Xml $converter, + \Magento\Analytics\ReportXml\Config\SchemaLocator $schemaLocator, + \Magento\Framework\Config\ValidationStateInterface $validationState, + $fileName = 'reports.xml', + $idAttributes = [], + $domDocumentClass = \Magento\Framework\Config\Dom::class, + $defaultScope = 'global' + ) { + parent::__construct( + $fileResolver, + $converter, + $schemaLocator, + $validationState, + $fileName, + $idAttributes, + $domDocumentClass, + $defaultScope + ); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/SchemaLocator.php b/app/code/Magento/Analytics/ReportXml/Config/SchemaLocator.php new file mode 100644 index 0000000000000..0a5a49e9b9744 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/SchemaLocator.php @@ -0,0 +1,51 @@ +urnResolver = $urnResolver; + } + + /** + * {@inheritdoc} + */ + public function getSchema() + { + return $this->urnResolver->getRealPath($this->realPath); + } + + /** + * {@inheritdoc} + */ + public function getPerFileSchema() + { + return null; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/ConfigInterface.php b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php new file mode 100644 index 0000000000000..f706321aa767c --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php @@ -0,0 +1,23 @@ +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 0000000000000..04764b92e91e2 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php @@ -0,0 +1,27 @@ +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 0000000000000..2ae835bb3344f --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php @@ -0,0 +1,63 @@ +nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + } + + /** + * 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->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 0000000000000..a457b8bd126a1 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php @@ -0,0 +1,105 @@ +conditionResolver = $conditionResolver; + $this->nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + } + + /** + * 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->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 0000000000000..57f08c236116a --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php @@ -0,0 +1,69 @@ +nameResolver = $nameResolver; + } + + /** + * 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 \Zend_Db_Expr( + strtoupper($attributeData['function']) . '(' . $prefix . $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 0000000000000..9eb506cbb105f --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php @@ -0,0 +1,163 @@ + '%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 \Zend_Db_Expr($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 \Zend_Db_Expr) { + $expression = $columns[$condition['attribute']]; + } else { + $expression = $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(' AND ', $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 0000000000000..52d8177cbb8a9 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php @@ -0,0 +1,40 @@ +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 0000000000000..303cd09262b0e --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php @@ -0,0 +1,64 @@ +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 0000000000000..efe958fee315e --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php @@ -0,0 +1,289 @@ +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 0000000000000..3fc12ccd0a39c --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php @@ -0,0 +1,43 @@ +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 0000000000000..2932d42228af2 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/IteratorFactory.php @@ -0,0 +1,61 @@ +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 + * + * < ... + * + * 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 0000000000000..2bdf2432600dd --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Query.php @@ -0,0 +1,96 @@ +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 json_encode, + * 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 0000000000000..001a35354cf0a --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/QueryFactory.php @@ -0,0 +1,142 @@ +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 0000000000000..9a8a8ffab0861 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ReportProvider.php @@ -0,0 +1,79 @@ +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 + * @param SearchCriteria|null $criteria + * @return \IteratorIterator + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getReport($name, SearchCriteria $criteria = null) + { + $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 0000000000000..b9a46fd286bd7 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/SelectHydrator.php @@ -0,0 +1,96 @@ +resourceConnection = $resourceConnection; + $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(); + foreach ($selectParts as $partName => $partValue) { + $select->setPart($partName, $partValue); + } + 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 0000000000000..b8d311ee5ea15 --- /dev/null +++ b/app/code/Magento/Analytics/Setup/InstallData.php @@ -0,0 +1,43 @@ +notificationTime = $notificationTime; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $this->notificationTime->storeLastTimeNotification(1); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BasicTier/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BasicTier/SignUpTest.php new file mode 100644 index 0000000000000..c9311ddf1ccc8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BasicTier/SignUpTest.php @@ -0,0 +1,84 @@ +configMock = $this->getMockBuilder(Config::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() + { + $basicTierUrlPath = 'analytics/url/basic_tier'; + $this->configMock->expects($this->once()) + ->method('getConfigDataValue') + ->with($basicTierUrlPath) + ->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 0000000000000..faedcedd2b5e8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php @@ -0,0 +1,155 @@ +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')], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/ActivateTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/ActivateTest.php new file mode 100644 index 0000000000000..a1cf41ace802c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/ActivateTest.php @@ -0,0 +1,229 @@ +resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultJsonMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subscriptionModelMock = $this->getMockBuilder(Subscription::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->notificationTimeMock = $this->getMockBuilder(NotificationTime::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->activateController = $this->objectManagerHelper->getObject( + Activate::class, + [ + 'resultFactory' => $this->resultFactoryMock, + 'subscription' => $this->subscriptionModelMock, + 'logger' => $this->loggerMock, + 'notificationTime' => $this->notificationTimeMock, + '_request' => $this->requestMock, + 'subscriptionApprovedFiled' => $this->subscriptionApprovedField, + ] + ); + } + + /** + * @dataProvider executeDataProvider + * + * @param bool $isSubscriptionEnabled + * @return void + */ + public function testExecute($isSubscriptionEnabled) + { + $successResult = [ + 'success' => true, + 'error_message' => '', + ]; + + $this->requestMock + ->expects($this->once()) + ->method('getParam') + ->with($this->subscriptionApprovedField) + ->willReturn($isSubscriptionEnabled); + + if ($isSubscriptionEnabled) { + $this->subscriptionModelMock + ->expects($this->once()) + ->method('enable') + ->willReturn(true); + } else { + $this->notificationTimeMock + ->expects($this->once()) + ->method('unsetLastTimeNotificationValue') + ->willReturn(true); + } + + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON) + ->willReturn($this->resultJsonMock); + $this->resultJsonMock->expects($this->once()) + ->method('setData') + ->with($successResult) + ->willReturnSelf(); + $this->assertSame( + $this->resultJsonMock, + $this->activateController->execute() + ); + } + + /** + * @dataProvider executeExceptionsDataProvider + * + * @param \Exception $exception + */ + public function testExecuteWithException(\Exception $exception) + { + $this->requestMock + ->expects($this->once()) + ->method('getParam') + ->with($this->subscriptionApprovedField) + ->willReturn(true); + + $this->subscriptionModelMock + ->expects($this->once()) + ->method('enable') + ->willThrowException($exception); + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($exception->getMessage()); + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON) + ->willReturn($this->resultJsonMock); + + if ($exception instanceof LocalizedException) { + $this->resultJsonMock + ->expects($this->once()) + ->method('setData') + ->with([ + 'success' => false, + 'error_message' => $exception->getMessage(), + ]) + ->willReturnSelf(); + } else { + $this->resultJsonMock + ->expects($this->once()) + ->method('setData') + ->withAnyParameters() + ->willReturnSelf(); + } + + $this->assertSame( + $this->resultJsonMock, + $this->activateController->execute() + ); + } + + /** + * @return array + */ + public function executeExceptionsDataProvider() + { + return [ + [new LocalizedException(__('TestMessage'))], + [new \Exception('TestMessage')], + ]; + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + [true], + [false], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/PostponeTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/PostponeTest.php new file mode 100644 index 0000000000000..78027298028d1 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/PostponeTest.php @@ -0,0 +1,168 @@ +dateTimeFactoryMock = $this->getMockBuilder(DateTimeFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dateTimeMock = $this->getMockBuilder(\DateTime::class) + ->disableOriginalConstructor() + ->getMock(); + $this->notificationTimeMock = $this->getMockBuilder(NotificationTime::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->getMock(); + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON) + ->willReturn($this->resultMock); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->action = $objectManagerHelper->getObject( + Postpone::class, + [ + 'resultFactory' => $this->resultFactoryMock, + 'dateTimeFactory' => $this->dateTimeFactoryMock, + 'notificationTime' => $this->notificationTimeMock, + 'logger' => $this->loggerMock + ] + ); + } + + public function testExecuteSuccess() + { + $this->dateTimeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->dateTimeMock); + $this->dateTimeMock->expects($this->once()) + ->method('getTimestamp') + ->willReturn(100500); + $this->notificationTimeMock->expects($this->once()) + ->method('storeLastTimeNotification') + ->with(100500) + ->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->dateTimeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->dateTimeMock); + $this->dateTimeMock->expects($this->once()) + ->method('getTimestamp') + ->willReturn(100500); + $this->notificationTimeMock->expects($this->once()) + ->method('storeLastTimeNotification') + ->with(100500) + ->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->dateTimeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->dateTimeMock); + $this->dateTimeMock->expects($this->once()) + ->method('getTimestamp') + ->willReturn(100500); + $this->notificationTimeMock->expects($this->once()) + ->method('storeLastTimeNotification') + ->with(100500) + ->willThrowException(new \Exception('Any message')); + $this->resultMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'success' => false, + 'error_message' => __('Error occurred during postponement notification') + ], + false, + [] + )->willReturnSelf(); + $this->assertEquals($this->resultMock, $this->action->execute()); + } +} 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 0000000000000..1b65a14659c86 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php @@ -0,0 +1,92 @@ +exportDataHandlerMock = $this->getMockBuilder(ExportDataHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $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 0000000000000..1b5d0d6a92b1c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php @@ -0,0 +1,171 @@ +connectorMock = $this->getMockBuilder(Connector::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inboxFactoryMock = $this->getMockBuilder(InboxFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->inboxResourceMock = $this->getMockBuilder(InboxResource::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inboxMock = $this->getMockBuilder(Inbox::class) + ->disableOriginalConstructor() + ->getMock(); + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->signUp = new SignUp( + $this->connectorMock, + $this->configWriterMock, + $this->inboxFactoryMock, + $this->inboxResourceMock, + $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->inboxFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->inboxMock); + $this->inboxMock->expects($this->once()) + ->method('addNotice'); + $this->inboxResourceMock->expects($this->once()) + ->method('save') + ->with($this->inboxMock); + $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 0000000000000..ba52571710212 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php @@ -0,0 +1,93 @@ +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->update = new Update( + $this->connectorMock, + $this->configWriterMock, + $this->reinitableConfigMock, + $this->flagManagerMock + ); + } + + public function testExecute() + { + $this->connectorMock->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn(true); + $this->configWriterMock->expects($this->once()) + ->method('delete') + ->with(BaseUrlConfigPlugin::UPDATE_CRON_STRING_PATH); + $this->flagManagerMock->expects($this->once()) + ->method('deleteFlag') + ->with(BaseUrlConfigPlugin::OLD_BASE_URL_FLAG_CODE); + $this->reinitableConfigMock->expects($this->once()) + ->method('reinit'); + $this->assertTrue($this->update->execute()); + } + + public function testExecuteUnsuccess() + { + $this->connectorMock->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn(false); + $this->assertFalse($this->update->execute()); + } +} 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 0000000000000..2409ec7c110ca --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php @@ -0,0 +1,129 @@ +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/Condition/CanViewNotificationTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Condition/CanViewNotificationTest.php new file mode 100644 index 0000000000000..f361fde03f02d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Condition/CanViewNotificationTest.php @@ -0,0 +1,81 @@ +dateTimeFactoryMock = $this->getMockBuilder(DateTimeFactory::class) + ->getMock(); + $this->dateTimeMock = $this->getMockBuilder(\DateTime::class) + ->getMock(); + $this->notificationTimeMock = $this->getMockBuilder(NotificationTime::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new ObjectManager($this); + $this->canViewNotification = $objectManager->getObject( + CanViewNotification::class, + [ + 'notificationTime' => $this->notificationTimeMock, + 'dateTimeFactory' => $this->dateTimeFactoryMock + ] + ); + } + + public function testValidate() + { + $this->notificationTimeMock->expects($this->once()) + ->method('getLastTimeNotification') + ->willReturn(1); + $this->dateTimeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->dateTimeMock); + $this->dateTimeMock->expects($this->once()) + ->method('getTimestamp') + ->willReturn(10005000); + $this->assertTrue($this->canViewNotification->validate()); + } + + public function testValidateFlagRemoved() + { + $this->notificationTimeMock->expects($this->once()) + ->method('getLastTimeNotification') + ->willReturn(null); + $this->dateTimeFactoryMock->expects($this->never()) + ->method('create'); + $this->assertFalse($this->canViewNotification->validate()); + } +} 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 0000000000000..132aa4aac9529 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php @@ -0,0 +1,112 @@ +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 0000000000000..f7a299cd4c657 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php @@ -0,0 +1,187 @@ +flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->tokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->notificationTimeMock = $this->getMockBuilder(NotificationTime::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configValueMock = $this->getMockBuilder(Value::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, + 'notificationTime' => $this->notificationTimeMock, + ] + ); + } + + /** + * @param int|null $value null means that $value was not changed + * @param bool $isTokenExist + * + * @dataProvider processDataProvider + */ + public function testProcess($value, $isTokenExist) + { + $this->configValueMock + ->expects($this->once()) + ->method('isValueChanged') + ->willReturn(is_int($value)); + $this->configValueMock + ->expects(is_int($value) ? $this->once() : $this->never()) + ->method('getData') + ->with('value') + ->willReturn($value); + $this->configWriterMock + ->expects(($value === 0) ? $this->once() : $this->never()) + ->method('delete') + ->with(CollectionTime::CRON_SCHEDULE_PATH); + $this->tokenMock + ->expects(is_int($value) ? $this->once() : $this->never()) + ->method('isTokenExist') + ->willReturn($isTokenExist); + if (is_int($value) && !$isTokenExist) { + if ($value === 1) { + $this->addProcessWithEnabledTrueAsserts(); + } elseif ($value === 0) { + $this->addProcessWithEnabledFalseAsserts(); + } + } + $this->assertTrue( + $this->subscriptionHandler->process($this->configValueMock) + ); + } + + /** + * Add assertions for method process in case when new config value equals 1. + * + * @return void + */ + private function addProcessWithEnabledTrueAsserts() + { + $this->configWriterMock + ->expects($this->once()) + ->with(SubscriptionHandler::CRON_STRING_PATH, "0 * * * *") + ->method('save'); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue) + ->willReturn(true); + $this->notificationTimeMock + ->expects($this->once()) + ->method('unsetLastTimeNotificationValue') + ->willReturn(true); + } + + /** + * Add assertions for method process in case when new config value equals 0. + * + * @return void + */ + private function addProcessWithEnabledFalseAsserts() + { + $this->flagManagerMock + ->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(true); + } + + /** + * Data provider for process test. + * + * @return array + */ + public function processDataProvider() + { + return [ + 'Config value has not changed and token exist' => [null, true], + 'Config value has not changed and token doesn\'t exist' => [null, false], + 'Config value is "No" and token exist' => [0, true], + 'Config value is "Yes" and token exist' => [1, true], + 'Config value is "No" and token doesn\'t exist' => [0, false], + ]; + } +} 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 0000000000000..336c80217b4c6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php @@ -0,0 +1,99 @@ +subscriptionHandlerMock = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->enabledModel = $this->objectManagerHelper->getObject( + Enabled::class, + [ + 'subscriptionHandler' => $this->subscriptionHandlerMock, + '_logger' => $this->loggerMock, + ] + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccess() + { + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('process') + ->with($this->enabledModel) + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testExecuteAfterSaveFailedWithLocalizedException() + { + $exception = new \Exception('Message'); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('process') + ->with($this->enabledModel) + ->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 0000000000000..72c70e3a85398 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php @@ -0,0 +1,59 @@ +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 0000000000000..d39ada40f811e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php @@ -0,0 +1,142 @@ +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 0000000000000..41883e2c8e3ad --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php @@ -0,0 +1,108 @@ +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/SchemaLocatorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/SchemaLocatorTest.php new file mode 100644 index 0000000000000..e486268e06bb2 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/SchemaLocatorTest.php @@ -0,0 +1,80 @@ +urnResolverMock = $this->getMockBuilder(UrnResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->schemaLocator = $this->objectManagerHelper->getObject( + SchemaLocator::class, + [ + 'urnResolver' => $this->urnResolverMock, + 'schema' => $this->schema, + ] + ); + } + + /** + * @return void + */ + public function testGetSchema() + { + $schemaRealPath = '/path/test.xml'; + + $this->urnResolverMock + ->expects($this->once()) + ->method('getRealPath') + ->with($this->schema) + ->willReturn($schemaRealPath); + + $this->assertSame($schemaRealPath, $this->schemaLocator->getSchema()); + } + + /** + * @return void + */ + public function testGetPerFileSchema() + { + $this->assertNull($this->schemaLocator->getPerFileSchema()); + } +} 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 0000000000000..0213e61d9ab35 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php @@ -0,0 +1,60 @@ +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 0000000000000..0876a9dba3db6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php @@ -0,0 +1,68 @@ +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 0000000000000..20f7e80bf0287 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php @@ -0,0 +1,203 @@ +curlMock = $this->getMockBuilder( + \Magento\Framework\HTTP\Adapter\Curl::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder( + \Psr\Log\LoggerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseMock = $this->getMockBuilder( + \Zend_Http_Response::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->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, + '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' => ['Content-Type: application/json'], + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + ] + ] + ]; + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestSuccess(array $data) + { + $responseString = 'This is response.'; + + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + $data['version'] + ); + $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($this->responseMock); + + $this->assertEquals( + $this->responseMock, + $this->subject->request( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + $data['version'] + ) + ); + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestError(array $data) + { + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + $data['version'] + ); + $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->assertFalse( + $this->subject->request( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + $data['version'] + ) + ); + } +} 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 0000000000000..f8faef12adc77 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php @@ -0,0 +1,275 @@ +loggerMock = $this->getMockBuilder( + \Psr\Log\LoggerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder( + \Magento\Framework\App\Config\ScopeConfigInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseMock = $this->getMockBuilder( + \Zend_Http_Response::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder( + \Magento\Analytics\Model\Connector\Http\ClientInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder( + \Magento\Analytics\Model\AnalyticsToken::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Connector\OTPRequest::class, + [ + 'analyticsToken' => $this->analyticsTokenMock, + 'config' => $this->configMock, + 'httpClient' => $this->httpClientMock, + 'logger' => $this->loggerMock + ] + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + 'otp' => 'thisisotp', + 'url' => 'http://www.mystore.com', + 'access-token' => 'thisisaccesstoken', + 'headers' => ['Content-Type: application/json'], + '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'], + $data['headers'] + ) + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->any()) + ->method('getStatus') + ->willReturn(201); + $this->responseMock->expects($this->any()) + ->method('getBody') + ->willReturn('{"otp": "' . $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 testCallTransportFailure() + { + $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'], + $data['headers'] + ) + ->willReturn(false); + + $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'], + $data['headers'] + ) + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->any()) + ->method('getStatus') + ->willReturn(409); + + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->assertFalse($this->subject->call()); + } + + /** + * @return void + */ + public function testCallException() + { + $data = $this->getTestData(); + + $exception = new \Exception('Test Exception'); + + $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'], + $data['headers'] + ) + ->willThrowException($exception); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); + + $this->assertFalse($this->subject->call()); + } +} 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 0000000000000..42d7ea0824ad5 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php @@ -0,0 +1,126 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationManagerMock = $this->getMockBuilder(IntegrationManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationToken = $this->getMockBuilder(IntegrationToken::class) + ->disableOriginalConstructor() + ->setMethods(['getToken']) + ->getMock(); + $this->integrationToken->expects($this->any()) + ->method('getToken') + ->willReturn('IntegrationToken'); + $this->signUpRequestMock = $this->getMockBuilder(SignUpRequest::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->signUpCommand = $objectManagerHelper->getObject( + SignUpCommand::class, + [ + 'analyticsToken' => $this->analyticsTokenMock, + 'integrationManager' => $this->integrationManagerMock, + 'signUpRequest' => $this->signUpRequestMock + ] + ); + } + + public function testExecuteSuccess() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn($this->integrationToken); + $this->integrationManagerMock->expects($this->once()) + ->method('activateIntegration') + ->willReturn(true); + $this->signUpRequestMock->expects($this->once()) + ->method('call') + ->with('IntegrationToken') + ->willReturn('MAToken'); + $this->analyticsTokenMock->expects($this->once()) + ->method('storeToken') + ->with('MAToken') + ->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') + ->willReturn(true); + $this->signUpRequestMock->expects($this->never()) + ->method('call') + ->willReturn('MAToken'); + $this->analyticsTokenMock->expects($this->never()) + ->method('storeToken') + ->willReturn(true); + $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); + $this->signUpRequestMock->expects($this->once()) + ->method('call') + ->with('IntegrationToken') + ->willReturn(false); + $this->analyticsTokenMock->expects($this->never()) + ->method('storeToken'); + $this->assertFalse($this->signUpCommand->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpRequestTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpRequestTest.php new file mode 100644 index 0000000000000..98ea13e092fa6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpRequestTest.php @@ -0,0 +1,226 @@ +loggerMock = $this->getMockBuilder( + \Psr\Log\LoggerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder( + \Magento\Config\Model\Config::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseMock = $this->getMockBuilder( + \Zend_Http_Response::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder( + \Magento\Analytics\Model\Connector\Http\ClientInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Connector\SignUpRequest::class, + [ + 'config' => $this->configMock, + 'httpClient' => $this->httpClientMock, + 'logger' => $this->loggerMock + ] + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + 'url' => 'http://www.mystore.com', + 'access-token' => 'thisisaccesstoken', + 'integration-token' => 'thisisintegrationtoken', + 'headers' => ['Content-Type: application/json'], + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + 'body'=> '{"token":"thisisintegrationtoken","url":"http:\/\/www.mystore.com"}', + ]; + } + + /** + * @return void + */ + public function testCallSuccess() + { + $data = $this->getTestData(); + + $this->configMock->expects($this->any()) + ->method('getConfigDataValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'], + $data['headers'] + ) + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->any()) + ->method('getStatus') + ->willReturn(201); + $this->responseMock->expects($this->any()) + ->method('getBody') + ->willReturn('{"access-token": "' . $data['access-token'] . '"}'); + + $this->assertEquals( + $data['access-token'], + $this->subject->call($data['integration-token']) + ); + } + + /** + * @return void + */ + public function testCallTransportFailure() + { + $data = $this->getTestData(); + + $this->configMock->expects($this->any()) + ->method('getConfigDataValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'], + $data['headers'] + ) + ->willReturn(false); + + $this->assertFalse( + $this->subject->call($data['integration-token']) + ); + } + + /** + * @return void + */ + public function testCallNoAccessToken() + { + $data = $this->getTestData(); + + $this->configMock->expects($this->any()) + ->method('getConfigDataValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'], + $data['headers'] + ) + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->any()) + ->method('getStatus') + ->willReturn(409); + + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->assertFalse( + $this->subject->call($data['integration-token']) + ); + } + + /** + * @return void + */ + public function testCallException() + { + $data = $this->getTestData(); + + $exception = new \Exception('Test Exception'); + + $this->configMock->expects($this->any()) + ->method('getConfigDataValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'], + $data['headers'] + ) + ->willThrowException($exception); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); + + $this->assertFalse( + $this->subject->call($data['integration-token']) + ); + } +} 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 0000000000000..f16944d2f3417 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php @@ -0,0 +1,139 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseMock = $this->getMockBuilder(\Zend_Http_Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->updateCommand = new UpdateCommand( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + $this->loggerMock, + $this->flagManagerMock + ); + } + + public function testExecuteSuccess() + { + $url = "old.localhost.com"; + $configVal = "Config val"; + $token = "Secret token!"; + $requestJson = sprintf('{"url":"%s","new-url":"%s","access-token":"%s"}', $url, $configVal, $token); + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + + $this->configMock->expects($this->any()) + ->method('getConfigDataValue') + ->willReturn($configVal); + + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(BaseUrlConfigPlugin::OLD_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, + $requestJson, + ['Content-Type: application/json'] + )->willReturn($this->responseMock); + + $this->responseMock->expects($this->once()) + ->method('getStatus') + ->willReturn(201); + + $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 0000000000000..a52186adf989e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php @@ -0,0 +1,70 @@ +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 0000000000000..4dccb54e192f5 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php @@ -0,0 +1,226 @@ +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 0000000000000..a3ea8b040c992 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php @@ -0,0 +1,61 @@ +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/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php new file mode 100644 index 0000000000000..21fe5fdf8081c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php @@ -0,0 +1,270 @@ +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 0000000000000..79617efff51ed --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php @@ -0,0 +1,194 @@ +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 0000000000000..282ab67a523dc --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php @@ -0,0 +1,62 @@ +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 0000000000000..244b46439a492 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php @@ -0,0 +1,209 @@ +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/FlagManagerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FlagManagerTest.php new file mode 100644 index 0000000000000..77abb0c87e1ea --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FlagManagerTest.php @@ -0,0 +1,123 @@ +flagFactoryMock = $this->getMockBuilder(FlagFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagResourceMock = $this->getMockBuilder(FlagResource::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagMock = $this->getMockBuilder(Flag::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManager = new FlagManager( + $this->flagFactoryMock, + $this->flagResourceMock + ); + } + + public function testGetFlagData() + { + $flagCode = "flag"; + $this->setupFlagObject($flagCode); + $this->flagMock->expects($this->once()) + ->method('getFlagData') + ->willReturn(10); + $this->assertEquals($this->flagManager->getFlagData($flagCode), 10); + } + + public function testSaveFlag() + { + $flagCode = "flag"; + $this->setupFlagObject($flagCode); + $this->flagMock->expects($this->once()) + ->method('setFlagData') + ->with(10); + $this->flagResourceMock->expects($this->once()) + ->method('save') + ->with($this->flagMock); + $this->assertTrue($this->flagManager->saveFlag($flagCode, 10)); + } + + /** + * @dataProvider flagExistDataProvider + * + * @param bool $isFlagExist + */ + public function testDeleteFlag($isFlagExist) + { + $flagCode = "flag"; + $this->setupFlagObject($flagCode); + $this->flagMock + ->expects($this->once()) + ->method('getId') + ->willReturn($isFlagExist); + if ($isFlagExist) { + $this->flagResourceMock + ->expects($this->once()) + ->method('delete') + ->with($this->flagMock); + } + $this->assertTrue($this->flagManager->deleteFlag($flagCode)); + } + + private function setupFlagObject($flagCode) + { + $this->flagFactoryMock->expects($this->once()) + ->method('create') + ->with(['data' => ['flag_code' => $flagCode]]) + ->willReturn($this->flagMock); + $this->flagResourceMock->expects($this->once()) + ->method('load') + ->with($this->flagMock, $flagCode, 'flag_code'); + } + + /** + * Provide variations of the flag existence. + * + * @return array + */ + public function flagExistDataProvider() + { + return [ + [true], + [false] + ]; + } +} 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 0000000000000..14af7c7e66e99 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php @@ -0,0 +1,228 @@ +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 0000000000000..e0ae69c975f7e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php @@ -0,0 +1,162 @@ +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, + [ + 'linkInterfaceFactory' => $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') + ->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->linkInterfaceMock->expects($this->once())->method('setUrl')->with( + $baseUrl . $fileInfoPath + ); + $this->linkInterfaceMock->expects($this->once()) + ->method('setInitializationVector') + ->with(base64_encode($fileInitializationVector)); + $this->assertEquals($this->linkInterfaceMock, $this->linkProvider->get()); + } + + /** + * @param string|null $fileInfoPath + * @param string|null $fileInitializationVector + * + * @dataProvider fileNotReadyDataProvider + * @expectedException \Magento\Framework\Webapi\Exception + * @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] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/NotificationTimeTest.php b/app/code/Magento/Analytics/Test/Unit/Model/NotificationTimeTest.php new file mode 100644 index 0000000000000..78bb694645fe8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/NotificationTimeTest.php @@ -0,0 +1,76 @@ +flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManagerHelper = new ObjectManagerHelper($this); + $this->notificationTime = $objectManagerHelper->getObject( + NotificationTime::class, + [ + 'flagManager' => $this->flagManagerMock, + ] + ); + } + + public function testStoreLastTimeNotification() + { + $value = 100500; + + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with(NotificationTime::NOTIFICATION_TIME, $value) + ->willReturn(true); + $this->assertTrue($this->notificationTime->storeLastTimeNotification($value)); + } + + public function testGetLastTimeNotification() + { + $value = 100500; + + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(NotificationTime::NOTIFICATION_TIME) + ->willReturn(true); + $this->assertEquals($value, $this->notificationTime->getLastTimeNotification()); + } + + public function testUnsetLastTimeNotificationValue() + { + $this->flagManagerMock + ->expects($this->once()) + ->method('deleteFlag') + ->with(NotificationTime::NOTIFICATION_TIME) + ->willReturn(true); + $this->assertTrue($this->notificationTime->unsetLastTimeNotificationValue()); + } +} 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 0000000000000..bf6456610f7a3 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php @@ -0,0 +1,190 @@ +flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configValueMock = $this->getMockBuilder(Baseurl::class) + ->disableOriginalConstructor() + ->getMock(); + $this->subscriptionStatusProvider = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->plugin = $this->objectManagerHelper->getObject( + BaseUrlConfigPlugin::class, + [ + 'flagManager' => $this->flagManagerMock, + 'subscriptionStatusProvider' => $this->subscriptionStatusProvider, + 'configWriter' => $this->configWriterMock + ] + ); + } + + /** + * @param array $testData + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $saveConfigInvokeMatcher + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $oldValueInvokeMatcher + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $saveFlagInvokeMatcher + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $configValueGetPathMatcher + * + * @return void + * @dataProvider pluginDataProvider + */ + public function testPluginForAfterSave( + array $testData, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $saveConfigInvokeMatcher, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $oldValueInvokeMatcher, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $saveFlagInvokeMatcher, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $configValueGetPathMatcher + ) { + $this->configValueMock->expects($this->once()) + ->method('isValueChanged') + ->willReturn($testData['isValueChanged']); + + $this->configValueMock->expects($configValueGetPathMatcher) + ->method('getData') + ->with('path') + ->willReturn($testData['path']); + $this->subscriptionStatusProvider->expects($this->any())->method('getStatus') + ->willReturn($testData['subscriptionStatus']); + + $oldUrl = 'mage.dev'; + $this->configValueMock->expects($oldValueInvokeMatcher) + ->method('getOldValue') + ->willReturn($oldUrl); + $this->flagManagerMock->expects($saveFlagInvokeMatcher) + ->method('saveFlag') + ->with(BaseUrlConfigPlugin::OLD_BASE_URL_FLAG_CODE, $oldUrl); + + $this->configWriterMock->expects($saveConfigInvokeMatcher)->method('save') + ->with( + BaseUrlConfigPlugin::UPDATE_CRON_STRING_PATH, + '0 * * * *' + ); + + $this->assertEquals( + $this->configValueMock, + $this->plugin->afterAfterSave($this->configValueMock, $this->configValueMock) + ); + } + + /** + * @return array + */ + public function pluginDataProvider() + { + return [ + 'setup_subscription_update_cron_job' => [ + 'testData' => [ + 'isValueChanged' => true, + 'subscriptionStatus' => SubscriptionStatusProvider::ENABLED, + 'path' => Store::XML_PATH_SECURE_BASE_URL + ], + 'saveConfigInvokeMatcher' => $this->once(), + 'oldValueInvokeMatcher' => $this->once(), + 'saveFlagInvokeMatcher' => $this->once(), + 'configValueGetPathMatcher' => $this->once(), + ], + 'base_url_not_changed' => [ + 'testData' => [ + 'isValueChanged' => false, + 'subscriptionStatus' => SubscriptionStatusProvider::ENABLED, + 'path' => Store::XML_PATH_SECURE_BASE_URL + ], + 'saveConfigInvokeMatcher' => $this->never(), + 'oldValueInvokeMatcher' => $this->never(), + 'saveFlagInvokeMatcher' => $this->never(), + 'configValueGetPathMatcher' => $this->never(), + ], + 'analytics_disabled' => [ + 'testData' => [ + 'isValueChanged' => true, + 'subscriptionStatus' => SubscriptionStatusProvider::DISABLED, + 'path' => Store::XML_PATH_SECURE_BASE_URL + ], + 'saveConfigInvokeMatcher' => $this->never(), + 'oldValueInvokeMatcher' => $this->never(), + 'saveFlagInvokeMatcher' => $this->never(), + 'configValueGetPathMatcher' => $this->once(), + ], + 'analytics_pending' => [ + 'testData' => [ + 'isValueChanged' => true, + 'subscriptionStatus' => SubscriptionStatusProvider::PENDING, + 'path' => Store::XML_PATH_SECURE_BASE_URL + ], + 'saveConfigInvokeMatcher' => $this->never(), + 'oldValueInvokeMatcher' => $this->never(), + 'saveFlagInvokeMatcher' => $this->never(), + 'configValueGetPathMatcher' => $this->once(), + ], + 'unsecure_url_changed' => [ + 'testData' => [ + 'isValueChanged' => true, + 'subscriptionStatus' => SubscriptionStatusProvider::PENDING, + 'path' => Store::XML_PATH_UNSECURE_BASE_URL + ], + 'saveConfigInvokeMatcher' => $this->never(), + 'oldValueInvokeMatcher' => $this->never(), + 'saveFlagInvokeMatcher' => $this->never(), + 'configValueGetPathMatcher' => $this->once(), + ] + ]; + } +} 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 0000000000000..4f3648a776b53 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php @@ -0,0 +1,122 @@ +configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->otpRequestMock = $this->getMockBuilder(OTPRequest::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportUrlProvider = $this->objectManagerHelper->getObject( + ReportUrlProvider::class, + [ + 'config' => $this->configMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'otpRequest' => $this->otpRequestMock, + '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'], + ]; + } +} 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 0000000000000..4f9a9aaf185a6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php @@ -0,0 +1,213 @@ +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 0000000000000..7bd6a0c5d5534 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php @@ -0,0 +1,50 @@ +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 0000000000000..13c76282b0b20 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php @@ -0,0 +1,123 @@ +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 0000000000000..4e32308d51e3c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php @@ -0,0 +1,94 @@ +systemConfigMock = $this->getMockBuilder(System::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->statusProvider = $this->objectManagerHelper->getObject( + SubscriptionStatusProvider::class, + [ + 'systemConfig' => $this->systemConfigMock, + 'analyticsToken' => $this->analyticsTokenMock + ] + ); + } + + /** + * @dataProvider statusDataProvider + * + * @param bool $isSubscriptionEnabled + * @param bool $hasToken + * @param int $attempts + * @param string $expectedStatus + */ + public function testGetStatus($isSubscriptionEnabled, $hasToken, $attempts, $expectedStatus) + { + $this->analyticsTokenMock->expects($this->exactly($attempts)) + ->method('isTokenExist') + ->willReturn($hasToken); + $this->systemConfigMock->expects($this->once()) + ->method('get') + ->with('default/analytics/subscription/enabled') + ->willReturn($isSubscriptionEnabled); + $this->assertEquals($expectedStatus, $this->statusProvider->getStatus()); + } + + /** + * @return array + */ + public function statusDataProvider() + { + return [ + 'TestWithEnabledStatus' => [true, true, 1, "Enabled"], + 'TestWithPendingStatus' => [true, false, 1, "Pending"], + 'TestWithDisabledStatus' => [false, false, 0, "Disabled"], + 'TestWithDisabledStatus2' => [false, true, 0, "Disabled"], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionTest.php b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionTest.php new file mode 100644 index 0000000000000..693b97cb0aff2 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionTest.php @@ -0,0 +1,197 @@ +configValueFactoryMock = $this->getMockBuilder(ValueFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configValueMock = $this->getMockBuilder(Value::class) + ->disableOriginalConstructor() + ->setMethods(['setValue', 'setPath']) + ->getMock(); + + $this->configStructureMock = $this->getMockBuilder(SearchInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configValueResourceMock = $this->getMockBuilder(AbstractDb::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->elementFieldMock = $this->getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->subscriptionModel = $this->objectManagerHelper->getObject( + SubscriptionModel::class, + [ + 'configValueFactory' => $this->configValueFactoryMock, + 'configStructure' => $this->configStructureMock, + 'configValueResource' => $this->configValueResourceMock, + 'reinitableConfig' => $this->reinitableConfigMock, + 'enabledConfigStructurePath' => $this->enableConfigStructurePath, + 'yesValueDropdown' => $this->yesValueDropdown, + ] + ); + } + + /** + * @dataProvider enabledDataProvider + * + * @param boolean $backendModel + * @param string $configPath + * + * @return void + */ + public function testEnabled($backendModel, $configPath) + { + $this->configStructureMock + ->expects($this->once()) + ->method('getElement') + ->with($this->enableConfigStructurePath) + ->willReturn($this->elementFieldMock); + $this->elementFieldMock + ->expects($this->once()) + ->method('hasBackendModel') + ->willReturn($backendModel); + if ($backendModel) { + $this->elementFieldMock + ->expects($this->once()) + ->method('getBackendModel') + ->willReturn($this->configValueMock); + } else { + $this->configValueFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->configValueMock); + } + $this->elementFieldMock + ->expects($this->once()) + ->method('getConfigPath') + ->willReturn($configPath); + $configPath = $configPath ?: $this->enableConfigStructurePath; + $this->configValueResourceMock + ->expects($this->once()) + ->method('load') + ->with($this->configValueMock, $configPath, 'path') + ->willReturnSelf(); + $this->configValueMock + ->expects($this->once()) + ->method('setValue') + ->with(1) + ->willReturnSelf(); + $this->configValueMock + ->expects($this->once()) + ->method('setPath') + ->with($configPath) + ->willReturnSelf(); + $this->configValueResourceMock + ->expects($this->once()) + ->method('save') + ->with($this->configValueMock) + ->willReturnSelf(); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->willReturnSelf(); + $this->assertTrue($this->subscriptionModel->enable()); + } + + /** + * @return array + */ + public function enabledDataProvider() + { + return [ + 'TestWithBackendModelWithoutConfigPath' => [true, null], + 'TestWithBackendModelWithConfigPath' => [true, $this->configPath], + 'TestWithoutBackendModelWithoutConfigPath' => [false, null], + 'TestWithoutBackendModelWithConfigPath' => [false, $this->configPath], + ]; + } +} 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 0000000000000..57e2514259c1c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php @@ -0,0 +1,121 @@ +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 0000000000000..b92ea2e866f74 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php @@ -0,0 +1,47 @@ +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/SchemaLocatorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/SchemaLocatorTest.php new file mode 100644 index 0000000000000..a4b14490a6fba --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/SchemaLocatorTest.php @@ -0,0 +1,74 @@ +urnResolverMock = $this->getMockBuilder(UrnResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->schemaLocator = $this->objectManagerHelper->getObject( + SchemaLocator::class, + [ + 'urnResolver' => $this->urnResolverMock, + 'realPath' => $this->examplePath, + ] + ); + } + + public function testGetSchema() + { + $schema = 'schema'; + + $this->urnResolverMock + ->expects($this->once()) + ->method('getRealPath') + ->with($this->examplePath) + ->willReturn($schema); + + $this->assertSame($schema, $this->schemaLocator->getSchema()); + } + + public function testGetPerFileSchema() + { + $this->assertNull($this->schemaLocator->getPerFileSchema()); + } +} 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 0000000000000..b1103893a25cf --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml @@ -0,0 +1,25 @@ + + + + + + + + 10 + + + + + + + + + + + + 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 0000000000000..21c4e6dd08019 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php @@ -0,0 +1,64 @@ +dataMock = $this->getMockBuilder(Data::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 0000000000000..9c88eb7616e30 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php @@ -0,0 +1,106 @@ +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 0000000000000..4266b7602e009 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php @@ -0,0 +1,143 @@ +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 0000000000000..e5e847648039d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php @@ -0,0 +1,121 @@ +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->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 + ] + ); + } + + /** + * @return void + */ + public function testAssemble() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'attribute' => [ + [ + 'name' => 'entity_id' + ] + ] + ] + ]; + + $this->nameResolverMock->expects($this->any()) + ->method('getAlias') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['alias']); + + $this->nameResolverMock->expects($this->any()) + ->method('getName') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['name']); + + $this->selectBuilderMock->expects($this->once()) + ->method('setFrom') + ->with([$queryConfigMock['source']['alias'] => $queryConfigMock['source']['name']]); + + $this->columnsResolverMock->expects($this->once()) + ->method('getColumns') + ->with($this->selectBuilderMock, $queryConfigMock['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, $queryConfigMock) + ); + } +} 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 0000000000000..f32f9dc3fa152 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php @@ -0,0 +1,261 @@ +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->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 + ] + ); + } + + /** + * @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 + * + * @dataProvider assembleNotEmptyDataProvider + * @return void + */ + public function testAssembleNotEmpty(array $queryConfigMock) + { + $filtersMock = []; + + $joinsMock = [ + 'billing' => [ + 'link-type' => 'left', + 'table' => [ + 'billing' => 'sales_order_address' + ], + 'condition' => '(billing.parent_id = `sales`.`entity_id`)' + ] + ]; + + $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->any()) + ->method('getName') + ->with($queryConfigMock['source']['link-source'][0]) + ->willReturn($queryConfigMock['source']['link-source'][0]['name']); + + $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' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + } +} 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 0000000000000..ae79f7536c504 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php @@ -0,0 +1,108 @@ +nameResolverMock = $this->getMockBuilder(NameResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder(SelectBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->columnsResolver = new ColumnsResolver($this->nameResolverMock); + } + + public function testGetColumnsWithoutAttributes() + { + $this->assertEquals($this->columnsResolver->getColumns($this->selectBuilderMock, []), []); + } + + /** + * @dataProvider dataProvider + */ + public function testGetColumns($expression, $attributeData) + { + $columnAlias = 'fn'; + $expr = new \Zend_Db_Expr($expression); + $expectedResult = [$columnAlias => $expr]; + $columns = [$columnAlias => 'name']; + $entityConfig['attribute'] = ['attribute1' => $attributeData]; + $this->selectBuilderMock->expects($this->once()) + ->method('getColumns') + ->willReturn($columns); + $this->nameResolverMock->expects($this->at(0)) + ->method('getAlias') + ->with($attributeData) + ->willReturn($columnAlias); + $this->nameResolverMock->expects($this->at(1)) + ->method('getAlias') + ->with($entityConfig) + ->willReturn($columnAlias); + $this->nameResolverMock->expects($this->once()) + ->method('getName') + ->with($attributeData) + ->willReturn('name'); + $group = ['g']; + $this->selectBuilderMock->expects($this->once()) + ->method('getGroup') + ->willReturn($group); + $this->selectBuilderMock->expects($this->once()) + ->method('setGroup') + ->with(array_merge($group, $expectedResult)); + $this->assertEquals( + $this->columnsResolver->getColumns( + $this->selectBuilderMock, + $entityConfig + ), + $expectedResult + ); + } + + public function dataProvider() + { + return [ + 'TestWithFunction' => + [ + 'expression' => "SUM( DISTINCT fn.name)", + 'attributeData' => ['adata1', 'function' => 'SUM', 'distinct' => true, 'group' => true], + ], + 'TestWithoutFunction' => [ + 'expression' => "fn.name", + 'attributeData' => ['adata1', 'distinct' => true, 'group' => true], + ], + ]; + } +} 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 0000000000000..1edf6a79ffa14 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php @@ -0,0 +1,102 @@ +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" => "3", + "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 \Zend_Db_Expr("(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->once()) + ->method('quoteIdentifier') + ->willReturn("'Smith'"); + $result = "(n.id != 1 OR ((n.first_name = 'John'))) AND (n.last_name = 'Smith')"; + $this->assertEquals( + $this->conditionResolver->getFilter($this->selectBuilderMock, $filterConfig, $aliasName), + $result + ); + } +} 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 0000000000000..9bcbd1dadbd87 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php @@ -0,0 +1,90 @@ +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 0000000000000..ae64b28cd97a0 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php @@ -0,0 +1,125 @@ +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 0000000000000..b070000ea9f9d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php @@ -0,0 +1,103 @@ +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 0000000000000..dee374b3bae05 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php @@ -0,0 +1,59 @@ +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 0000000000000..1b8502ad0fc53 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php @@ -0,0 +1,239 @@ +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 0000000000000..9b5a4bca74b2b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php @@ -0,0 +1,90 @@ +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 0000000000000..ee20a59074485 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php @@ -0,0 +1,180 @@ +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 0000000000000..e6f6c5144d338 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php @@ -0,0 +1,113 @@ +resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectHydrator = new SelectHydrator($this->resourceConnectionMock); + } + + 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); + } + + public function testRecreate() + { + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->selectMock); + $parts = + [ + 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 + ]; + $selectParts = []; + foreach ($parts as $key => $part) { + $this->selectMock->expects($this->at($key)) + ->method('setPart') + ->with($part, 'part' . $key); + $selectParts[$part] = 'part' . $key; + } + $this->selectHydrator->recreate($selectParts); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Ui/DataProvider/DummyDataProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Ui/DataProvider/DummyDataProviderTest.php new file mode 100644 index 0000000000000..8e0cd06ef272b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Ui/DataProvider/DummyDataProviderTest.php @@ -0,0 +1,228 @@ + 'value']; + + /** + * @return void + */ + protected function setUp() + { + $this->searchResultMock = $this->getMockBuilder(SearchResultInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->searchCriteriaMock = $this->getMockBuilder(SearchCriteriaInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->dataCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->filterMock = $this->getMockBuilder(Filter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->dummyDataProvider = $this->objectManagerHelper->getObject( + DummyDataProvider::class, + [ + 'name' => $this->providerName, + 'searchResult' => $this->searchResultMock, + 'searchCriteria' => $this->searchCriteriaMock, + 'collection' => $this->dataCollectionMock, + 'data' => ['config' => $this->configData], + ] + ); + } + + /** + * @return void + */ + public function testGetName() + { + $this->assertSame($this->providerName, $this->dummyDataProvider->getName()); + } + + /** + * @return void + */ + public function testGetConfigData() + { + $this->assertSame($this->configData, $this->dummyDataProvider->getConfigData()); + $dataProvider = $this->objectManagerHelper + ->getObject( + DummyDataProvider::class, + [] + ); + $this->assertSame([], $dataProvider->getConfigData()); + } + + /** + * @return void + */ + public function testSetConfigData() + { + $configValue = ['key' => 'value']; + + $this->assertTrue($this->dummyDataProvider->setConfigData($configValue)); + $this->assertSame($configValue, $this->dummyDataProvider->getConfigData()); + } + + /** + * @return void + */ + public function testGetMeta() + { + $this->assertSame([], $this->dummyDataProvider->getMeta()); + } + + /** + * @return void + */ + public function testGetFieldMetaInfo() + { + $this->assertSame([], $this->dummyDataProvider->getFieldMetaInfo('', '')); + } + + /** + * @return void + */ + public function testGetFieldSetMetaInfo() + { + $this->assertSame([], $this->dummyDataProvider->getFieldSetMetaInfo('')); + } + + /** + * @return void + */ + public function testGetFieldsMetaInfo() + { + $this->assertSame([], $this->dummyDataProvider->getFieldsMetaInfo('')); + } + + /** + * @return void + */ + public function testGetPrimaryFieldName() + { + $this->assertSame('', $this->dummyDataProvider->getPrimaryFieldName()); + } + + /** + * @return void + */ + public function testGetRequestFieldName() + { + $this->assertSame('', $this->dummyDataProvider->getRequestFieldName()); + } + + /** + * @return void + */ + public function testGetData() + { + $this->dataCollectionMock + ->expects($this->once()) + ->method('toArray') + ->willReturn([]); + $this->assertSame([], $this->dummyDataProvider->getData()); + } + + /** + * @return void + */ + public function testAddFilter() + { + $this->assertNull($this->dummyDataProvider->addFilter($this->filterMock)); + } + + /** + * @return void + */ + public function testAddOrder() + { + $this->assertNull($this->dummyDataProvider->addOrder('', '')); + } + + /** + * @return void + */ + public function testSetLimit() + { + $this->assertNull($this->dummyDataProvider->setLimit(1, 1)); + } + + /** + * @return void + */ + public function testGetSearchCriteria() + { + $this->assertSame($this->searchCriteriaMock, $this->dummyDataProvider->getSearchCriteria()); + } + + /** + * @return void + */ + public function testGetSearchResult() + { + $this->assertSame($this->searchResultMock, $this->dummyDataProvider->getSearchResult()); + } +} diff --git a/app/code/Magento/Analytics/Ui/DataProvider/DummyDataProvider.php b/app/code/Magento/Analytics/Ui/DataProvider/DummyDataProvider.php new file mode 100644 index 0000000000000..e842b6572b8c2 --- /dev/null +++ b/app/code/Magento/Analytics/Ui/DataProvider/DummyDataProvider.php @@ -0,0 +1,237 @@ +name = $name; + $this->searchResult = $searchResult; + $this->searchCriteria = $searchCriteria; + $this->collection = $collection; + $this->data = $data; + } + + /** + * Get Data Provider name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get config data + * + * @return mixed + */ + public function getConfigData() + { + return isset($this->data['config']) ? $this->data['config'] : []; + } + + /** + * Set config data + * + * @param mixed $config + * + * @return bool + */ + public function setConfigData($config) + { + $this->data['config'] = $config; + + return true; + } + + /** + * @return array + */ + public function getMeta() + { + return []; + } + + /** + * @param string $fieldSetName + * @param string $fieldName + * + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldMetaInfo($fieldSetName, $fieldName) + { + return []; + } + + /** + * Get field set meta info + * + * @param string $fieldSetName + * + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldSetMetaInfo($fieldSetName) + { + return []; + } + + /** + * @param string $fieldSetName + * + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldsMetaInfo($fieldSetName) + { + return []; + } + + /** + * Get primary field name + * + * @return string + */ + public function getPrimaryFieldName() + { + return ''; + } + + /** + * Get field name in request + * + * @return string + */ + public function getRequestFieldName() + { + return ''; + } + + /** + * Get data + * + * @return mixed + */ + public function getData() + { + return $this->collection->toArray(); + } + + /** + * Add field filter to collection + * + * @param \Magento\Framework\Api\Filter $filter + * + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function addFilter(\Magento\Framework\Api\Filter $filter) + { + } + + /** + * Add ORDER BY to the end or to the beginning + * + * @param string $field + * @param string $direction + * + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function addOrder($field, $direction) + { + } + + /** + * Set Query limit + * + * @param int $offset + * @param int $size + * + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setLimit($offset, $size) + { + } + + /** + * Returns search criteria + * + * @return SearchCriteriaInterface + */ + public function getSearchCriteria() + { + return $this->searchCriteria; + } + + /** + * @return SearchResultInterface + */ + public function getSearchResult() + { + return $this->searchResult; + } +} diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json new file mode 100644 index 0000000000000..b8d05bbd08a52 --- /dev/null +++ b/app/code/Magento/Analytics/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-analytics", + "description": "N/A", + "require": { + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", + "magento/module-backend": "100.2.*", + "magento/module-admin-notification": "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/etc/acl.xml b/app/code/Magento/Analytics/etc/acl.xml new file mode 100644 index 0000000000000..9dffb87bbabc0 --- /dev/null +++ b/app/code/Magento/Analytics/etc/acl.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000..0d874688521da --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/menu.xml @@ -0,0 +1,13 @@ + + + + + + + + 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 0000000000000..232ebe94d314f --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + 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 0000000000000..c3e15c0ba3c20 --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -0,0 +1,43 @@ + + + + +
    + + general + Magento_Analytics::analytics_settings + + + + + Magento\Config\Model\Config\Source\Yesno + Magento\Analytics\Model\Config\Backend\Enabled + analytics/subscription/enabled + + + Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel + + + + Magento\Analytics\Model\Config\Source\Vertical + Magento\Analytics\Model\Config\Backend\Vertical + + + + + + Magento\Analytics\Model\Config\Backend\CollectionTime + + + 1 + + + +
    +
    +
    diff --git a/app/code/Magento/Analytics/etc/analytics.xml b/app/code/Magento/Analytics/etc/analytics.xml new file mode 100644 index 0000000000000..d35df54bffe7e --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xml @@ -0,0 +1,50 @@ + + + + + + + + modules + + + + + + + + + + + + + + stores + + + + + + + + + websites + + + + + + + + + groups + + + + + diff --git a/app/code/Magento/Analytics/etc/analytics.xsd b/app/code/Magento/Analytics/etc/analytics.xsd new file mode 100644 index 0000000000000..da3357f8eb75a --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xsd @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + File name attribute can has only [a-zA-Z0-9/_]. + + + + + + + + + + Value is required. + + + + + + + diff --git a/app/code/Magento/Analytics/etc/config.xml b/app/code/Magento/Analytics/etc/config.xml new file mode 100644 index 0000000000000..8a55adbdf730e --- /dev/null +++ b/app/code/Magento/Analytics/etc/config.xml @@ -0,0 +1,26 @@ + + + + + + + https://advancedreporting.rjmetrics.com/signup + https://advancedreporting.rjmetrics.com/update + https://dashboard.rjmetrics.com/v2/magento/signup + https://advancedreporting.rjmetrics.com/otp + https://advancedreporting.rjmetrics.com/report + + Magento Analytics user + + + 02,00,00 + + + + + diff --git a/app/code/Magento/Analytics/etc/crontab.xml b/app/code/Magento/Analytics/etc/crontab.xml new file mode 100644 index 0000000000000..ae74693df98ae --- /dev/null +++ b/app/code/Magento/Analytics/etc/crontab.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/di.xml b/app/code/Magento/Analytics/etc/di.xml new file mode 100644 index 0000000000000..4fe2693afcda8 --- /dev/null +++ b/app/code/Magento/Analytics/etc/di.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + Magento\Analytics\Model\Connector\SignUpCommand + Magento\Analytics\Model\Connector\UpdateCommand + + + + + + + Magento\Analytics\Model\Condition\CanViewNotification + + + + + + Magento\Config\Model\ResourceModel\Config\Data + + + + + + Magento\Analytics\ReportXml\Config\Reader\Xml + + + + + + + Magento\Analytics\ReportXml\DB\Assembler\FromAssembler + Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler + Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler + + + + + + + Magento\Analytics\Model\Config\Reader\Xml + + + + + + Magento\Analytics\Model\Config\Data + + + + + Magento\Analytics\Model\Config\SchemaLocator + Magento\Analytics\ReportXml\Config\Converter\Xml + + + + + Magento\Analytics\Model\Config\Reader + + + + + + web/unsecure/base_url + currency/options/base + general/locale/timezone + general/country/default + carriers/dhl/title + carriers/dhl/active + carriers/fedex/title + carriers/fedex/active + carriers/flatrate/title + carriers/flatrate/active + carriers/tablerate/title + carriers/tablerate/active + carriers/freeshipping/title + carriers/freeshipping/active + carriers/ups/title + carriers/ups/active + carriers/usps/title + carriers/usps/active + payment/free/title + payment/free/active + payment/checkmo/title + payment/checkmo/active + payment/purchaseorder/title + payment/purchaseorder/active + payment/banktransfer/title + payment/banktransfer/active + payment/cashondelivery/title + payment/cashondelivery/active + payment/authorizenet_directpost/title + payment/authorizenet_directpost/active + payment/paypal_billing_agreement/title + payment/paypal_billing_agreement/active + payment/braintree/title + payment/braintree/active + payment/braintree_paypal/title + payment/braintree_paypal/active + + + + + + + Apps and Games + Athletic/Sporting Goods + Art and Design + Auto Parts + Baby/Children’s Apparel, Gear and Toys + Beauty and Cosmetics + Books, Music and Magazines + Crafts and Stationery + Consumer Electronics + Deal Site + Fashion Apparel and Accessories + Food, Beverage and Grocery + Home Goods and Furniture + Home Improvement + Jewelry and Watches + Mass Merchant + Office Supplies + Outdoor and Camping Gear + Pet Goods + Pharma and Medical Devices + Technology B2B + Other + + + + + + + diff --git a/app/code/Magento/Analytics/etc/module.xml b/app/code/Magento/Analytics/etc/module.xml new file mode 100644 index 0000000000000..958d43cd6ee53 --- /dev/null +++ b/app/code/Magento/Analytics/etc/module.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/reports.xml b/app/code/Magento/Analytics/etc/reports.xml new file mode 100644 index 0000000000000..1c9f4a3d62700 --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 0000000000000..e7e341f5d9a36 --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xsd @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/webapi.xml b/app/code/Magento/Analytics/etc/webapi.xml new file mode 100644 index 0000000000000..401d09b06563d --- /dev/null +++ b/app/code/Magento/Analytics/etc/webapi.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/app/code/Magento/Analytics/registration.php b/app/code/Magento/Analytics/registration.php new file mode 100644 index 0000000000000..d07fe8ffb5e56 --- /dev/null +++ b/app/code/Magento/Analytics/registration.php @@ -0,0 +1,11 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/link.phtml b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/link.phtml new file mode 100644 index 0000000000000..6f7874b5c001e --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/link.phtml @@ -0,0 +1,13 @@ + + + escapeHtml(__('Free Tier')) ?> + diff --git a/app/code/Magento/Analytics/view/adminhtml/ui_component/analytics_subscription_form.xml b/app/code/Magento/Analytics/view/adminhtml/ui_component/analytics_subscription_form.xml new file mode 100644 index 0000000000000..60f3751d74bd4 --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/ui_component/analytics_subscription_form.xml @@ -0,0 +1,114 @@ + + +
    + + + analytics_subscription_form.analytics_subscription_form_data_source + analytics_subscription_form.analytics_subscription_form_data_source + + Analytics Subscription + + data + analytics_subscription_form + simple + true + + templates/form/collapsible + + + + + Magento\Analytics\Ui\DataProvider\DummyDataProvider + analytics_subscription_form_data_source + + + + + + false + + + + + + + Magento_Ui/js/form/provider + + + + + + + + Magento_Analytics/js/modal/modal-component + actionCancel + true + + Subscription Confirmation + popup + true + true + true + + + Cancel + action-secondary + + + ${ $.name } + actionCancel + + + + + Ok + action-primary + + + ${ $.parentName } + save + + + ${ $.name } + closeModal + + + + + + + + +
    + + + + + + + + + + admin__field-wide + I agree with sending my system + configuration and transaction data to Magento Analytics. + + checkbox + analytics_subscription_checkbox + + 1 + 0 + + 1 + + + +
    +
    +
    diff --git a/app/code/Magento/Analytics/view/adminhtml/web/js/modal/modal-component.js b/app/code/Magento/Analytics/view/adminhtml/web/js/modal/modal-component.js new file mode 100644 index 0000000000000..4b215db24857a --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/web/js/modal/modal-component.js @@ -0,0 +1,66 @@ +/** + * Copyright © 2013-2017 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: { + postponeOptions: {}, + imports: { + postponeUrl: '${ $.provider }:postpone_url' + }, + modules: { + form: '${ $.parentName }' + } + }, + + /** + * Send request to postpone modal appearance for a certain time. + * + * @param {Object} options - additional request options. + */ + sendPostponeRequest: function (options) { + var self = this, + data = $.extend(this.form().source.data, options); + + $.ajax({ + type: 'POST', + url: this.postponeUrl, + data: data, + showLoader: true + }).done(function (xhr) { + if (xhr.error) { + self.onError(xhr); + } + }).fail(this.onError); + }, + + /** + * Error handler. + * + * @param {Object} xhr - request result. + */ + onError: function (xhr) { + if (xhr.statusText === 'abort') { + return; + } + + alert({ + content: xhr.message || $t('An error occurred while subscription process.') + }); + }, + + /** @inheritdoc */ + actionCancel: function () { + this.sendPostponeRequest(this.postponeOptions); + this.closeModal(); + } + }); +}); diff --git a/app/code/Magento/Authorization/Model/Acl/AclRetriever.php b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php index d3a5714c93743..5b67748cd2e41 100644 --- a/app/code/Magento/Authorization/Model/Acl/AclRetriever.php +++ b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php @@ -1,6 +1,6 @@ _resource = $resource; $this->_groupFactory = $groupFactory; $this->_roleFactory = $roleFactory; + $this->aclDataCache = $aclDataCache ?: ObjectManager::getInstance()->get( + \Magento\Framework\Acl\Data\CacheInterface::class + ); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->cacheKey = $cacheKey; } /** @@ -49,12 +81,7 @@ public function __construct( */ public function populateAcl(\Magento\Framework\Acl $acl) { - $roleTableName = $this->_resource->getTableName('authorization_role'); - $connection = $this->_resource->getConnection(); - - $select = $connection->select()->from($roleTableName)->order('tree_level'); - - foreach ($connection->fetchAll($select) as $role) { + foreach ($this->getRolesArray() as $role) { $parent = $role['parent_id'] > 0 ? $role['parent_id'] : null; switch ($role['role_type']) { case RoleGroup::ROLE_TYPE: @@ -71,4 +98,28 @@ public function populateAcl(\Magento\Framework\Acl $acl) } } } + + /** + * Get application ACL roles array + * + * @return array + */ + private function getRolesArray() + { + $rolesCachedData = $this->aclDataCache->load($this->cacheKey); + if ($rolesCachedData) { + return $this->serializer->unserialize($rolesCachedData); + } + + $roleTableName = $this->_resource->getTableName('authorization_role'); + $connection = $this->_resource->getConnection(); + + $select = $connection->select() + ->from($roleTableName) + ->order('tree_level'); + + $rolesArray = $connection->fetchAll($select); + $this->aclDataCache->save($this->serializer->serialize($rolesArray), $this->cacheKey); + return $rolesArray; + } } diff --git a/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php b/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php index 2fa86b63ae504..8f89e00cfb22e 100644 --- a/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php +++ b/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php @@ -1,32 +1,69 @@ _resource = $resource; $this->_rootResource = $rootResource; + $this->aclDataCache = $aclDataCache ?: ObjectManager::getInstance()->get( + \Magento\Framework\Acl\Data\CacheInterface::class + ); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->cacheKey = $cacheKey; } /** @@ -37,15 +74,7 @@ public function __construct( */ public function populateAcl(\Magento\Framework\Acl $acl) { - $ruleTable = $this->_resource->getTableName("authorization_rule"); - - $connection = $this->_resource->getConnection(); - - $select = $connection->select()->from(['r' => $ruleTable]); - - $rulesArr = $connection->fetchAll($select); - - foreach ($rulesArr as $rule) { + foreach ($this->getRulesArray() as $rule) { $role = $rule['role_id']; $resource = $rule['resource_id']; $privileges = !empty($rule['privileges']) ? explode(',', $rule['privileges']) : null; @@ -62,4 +91,28 @@ public function populateAcl(\Magento\Framework\Acl $acl) } } } + + /** + * Get application ACL rules array. + * + * @return array + */ + private function getRulesArray() + { + $rulesCachedData = $this->aclDataCache->load($this->cacheKey); + if ($rulesCachedData) { + return $this->serializer->unserialize($rulesCachedData); + } + + $ruleTable = $this->_resource->getTableName("authorization_rule"); + $connection = $this->_resource->getConnection(); + $select = $connection->select() + ->from(['r' => $ruleTable]); + + $rulesArr = $connection->fetchAll($select); + + $this->aclDataCache->save($this->serializer->serialize($rulesArr), $this->cacheKey); + + return $rulesArr; + } } diff --git a/app/code/Magento/Authorization/Model/Acl/Role/Generic.php b/app/code/Magento/Authorization/Model/Acl/Role/Generic.php index f35a755b74045..b6a92d5ccae27 100644 --- a/app/code/Magento/Authorization/Model/Acl/Role/Generic.php +++ b/app/code/Magento/Authorization/Model/Acl/Role/Generic.php @@ -1,6 +1,6 @@ _aclBuilder = $aclBuilder; parent::__construct($context, $connectionName); $this->_rootResource = $rootResource; $this->_aclCache = $aclCache; $this->_logger = $logger; + $this->aclDataCache = $aclDataCache ?: ObjectManager::getInstance()->get( + \Magento\Framework\Acl\Data\CacheInterface::class + ); } /** @@ -79,8 +92,8 @@ protected function _construct() */ public function saveRel(\Magento\Authorization\Model\Rules $rule) { + $connection = $this->getConnection(); try { - $connection = $this->getConnection(); $connection->beginTransaction(); $roleId = $rule->getRoleId(); @@ -118,7 +131,7 @@ public function saveRel(\Magento\Authorization\Model\Rules $rule) } $connection->commit(); - $this->_aclCache->clean(); + $this->aclDataCache->clean(); } catch (\Magento\Framework\Exception\LocalizedException $e) { $connection->rollBack(); throw $e; diff --git a/app/code/Magento/Authorization/Model/ResourceModel/Rules/Collection.php b/app/code/Magento/Authorization/Model/ResourceModel/Rules/Collection.php index e4bb8f1195322..9aec756931868 100644 --- a/app/code/Magento/Authorization/Model/ResourceModel/Rules/Collection.php +++ b/app/code/Magento/Authorization/Model/ResourceModel/Rules/Collection.php @@ -1,6 +1,6 @@ _resourceMock = $this->getMock( @@ -57,51 +72,82 @@ protected function setUp() false ); - $this->_resourceMock->expects( - $this->once() - )->method( - 'getTableName' - )->with( - $this->equalTo('authorization_role') - )->will( - $this->returnArgument(1) - ); - - $selectMock = $this->getMock(\Magento\Framework\DB\Select::class, [], [], '', false); - $selectMock->expects($this->any())->method('from')->will($this->returnValue($selectMock)); + $this->selectMock = $this->getMock(\Magento\Framework\DB\Select::class, [], [], '', false); + $this->selectMock->expects($this->any()) + ->method('from') + ->will($this->returnValue($this->selectMock)); $this->_adapterMock = $this->getMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, [], [], '', false); - $this->_adapterMock->expects($this->once())->method('select')->will($this->returnValue($selectMock)); - - $this->_resourceMock->expects( - $this->once() - )->method( - 'getConnection' - )->will( - $this->returnValue($this->_adapterMock) + + $this->serializerMock = $this->getMock( + \Magento\Framework\Serialize\Serializer\Json::class, + ['serialize', 'unserialize'], + [], + '', + false + ); + $this->serializerMock->expects($this->any()) + ->method('serialize') + ->will( + $this->returnCallback( + function ($value) { + return json_encode($value); + } + ) + ); + + $this->serializerMock->expects($this->any()) + ->method('unserialize') + ->will( + $this->returnCallback( + function ($value) { + return json_decode($value, true); + } + ) + ); + + $this->aclDataCacheMock = $this->getMock( + \Magento\Framework\Acl\Data\CacheInterface::class, + [], + [], + '', + false ); $this->_model = new \Magento\Authorization\Model\Acl\Loader\Role( $this->_groupFactoryMock, $this->_roleFactoryMock, - $this->_resourceMock + $this->_resourceMock, + $this->aclDataCacheMock, + $this->serializerMock ); } public function testPopulateAclAddsRolesAndTheirChildren() { - $this->_adapterMock->expects( - $this->once() - )->method( - 'fetchAll' - )->will( - $this->returnValue( - [ - ['role_id' => 1, 'role_type' => 'G', 'parent_id' => null], - ['role_id' => 2, 'role_type' => 'U', 'parent_id' => 1, 'user_id' => 1], - ] - ) - ); + $this->_resourceMock->expects($this->once()) + ->method('getTableName') + ->with($this->equalTo('authorization_role')) + ->will($this->returnArgument(1)); + + $this->_adapterMock->expects($this->once()) + ->method('select') + ->will($this->returnValue($this->selectMock)); + + $this->_resourceMock->expects($this->once()) + ->method('getConnection') + ->will($this->returnValue($this->_adapterMock)); + + $this->_adapterMock->expects($this->once()) + ->method('fetchAll') + ->will( + $this->returnValue( + [ + ['role_id' => 1, 'role_type' => 'G', 'parent_id' => null], + ['role_id' => 2, 'role_type' => 'U', 'parent_id' => 1, 'user_id' => 1], + ] + ) + ); $this->_groupFactoryMock->expects($this->once())->method('create')->with(['roleId' => '1']); $this->_roleFactoryMock->expects($this->once())->method('create')->with(['roleId' => '2']); @@ -115,13 +161,55 @@ public function testPopulateAclAddsRolesAndTheirChildren() public function testPopulateAclAddsMultipleParents() { - $this->_adapterMock->expects( - $this->once() - )->method( - 'fetchAll' - )->will( - $this->returnValue([['role_id' => 1, 'role_type' => 'U', 'parent_id' => 2, 'user_id' => 3]]) - ); + $this->_resourceMock->expects($this->once()) + ->method('getTableName') + ->with($this->equalTo('authorization_role')) + ->will($this->returnArgument(1)); + + $this->_adapterMock->expects($this->once()) + ->method('select') + ->will($this->returnValue($this->selectMock)); + + $this->_resourceMock->expects($this->once()) + ->method('getConnection') + ->will($this->returnValue($this->_adapterMock)); + + $this->_adapterMock->expects($this->once()) + ->method('fetchAll') + ->will($this->returnValue([['role_id' => 1, 'role_type' => 'U', 'parent_id' => 2, 'user_id' => 3]])); + + $this->_roleFactoryMock->expects($this->never())->method('getModelInstance'); + $this->_groupFactoryMock->expects($this->never())->method('getModelInstance'); + + $aclMock = $this->getMock(\Magento\Framework\Acl::class); + $aclMock->expects($this->at(0))->method('hasRole')->with('1')->will($this->returnValue(true)); + $aclMock->expects($this->at(1))->method('addRoleParent')->with('1', '2'); + + $this->_model->populateAcl($aclMock); + } + + public function testPopulateAclFromCache() + { + $this->_resourceMock->expects($this->never())->method('getConnection'); + $this->_resourceMock->expects($this->never())->method('getTableName'); + $this->_adapterMock->expects($this->never())->method('fetchAll'); + $this->aclDataCacheMock->expects($this->once()) + ->method('load') + ->with(\Magento\Authorization\Model\Acl\Loader\Role::ACL_ROLES_CACHE_KEY) + ->will( + $this->returnValue( + json_encode( + [ + [ + 'role_id' => 1, + 'role_type' => 'U', + 'parent_id' => 2, + 'user_id' => 3 + ] + ] + ) + ) + ); $this->_roleFactoryMock->expects($this->never())->method('getModelInstance'); $this->_groupFactoryMock->expects($this->never())->method('getModelInstance'); diff --git a/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php b/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php index f5f3b9b6a4b9f..2cf4b6d997fee 100644 --- a/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php +++ b/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php @@ -1,6 +1,6 @@ _resourceMock = $this->getMock( @@ -32,39 +42,71 @@ protected function setUp() false, false ); + $this->serializerMock = $this->getMock( + \Magento\Framework\Serialize\Serializer\Json::class, + ['serialize', 'unserialize'], + [], + '', + false + ); + $this->serializerMock->expects($this->any()) + ->method('serialize') + ->will( + $this->returnCallback( + function ($value) { + return json_encode($value); + } + ) + ); + + $this->serializerMock->expects($this->any()) + ->method('unserialize') + ->will( + $this->returnCallback( + function ($value) { + return json_decode($value, true); + } + ) + ); + + $this->aclDataCacheMock = $this->getMock( + \Magento\Framework\Acl\Data\CacheInterface::class, + [], + [], + '', + false + ); + $this->_rootResourceMock = new \Magento\Framework\Acl\RootResource('Magento_Backend::all'); $this->_model = new \Magento\Authorization\Model\Acl\Loader\Rule( $this->_rootResourceMock, - $this->_resourceMock + $this->_resourceMock, + [], + $this->aclDataCacheMock, + $this->serializerMock ); } - public function testPopulateAcl() + public function testPopulateAclFromCache() { - $this->_resourceMock->expects($this->any())->method('getTable')->will($this->returnArgument(1)); - - $selectMock = $this->getMock(\Magento\Framework\DB\Select::class, [], [], '', false); - $selectMock->expects($this->any())->method('from')->will($this->returnValue($selectMock)); - - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, [], [], '', false); - $connectionMock->expects($this->once())->method('select')->will($this->returnValue($selectMock)); - $connectionMock->expects( - $this->once() - )->method( - 'fetchAll' - )->will( - $this->returnValue( - [ - ['role_id' => 1, 'resource_id' => 'Magento_Backend::all', 'permission' => 'allow'], - ['role_id' => 2, 'resource_id' => 1, 'permission' => 'allow'], - ['role_id' => 3, 'resource_id' => 1, 'permission' => 'deny'], - ] - ) - ); + $this->_resourceMock->expects($this->never())->method('getTable'); + $this->_resourceMock->expects($this->never()) + ->method('getConnection'); - $this->_resourceMock->expects($this->once()) - ->method('getConnection') - ->will($this->returnValue($connectionMock)); + $this->aclDataCacheMock->expects($this->once()) + ->method('load') + ->with(\Magento\Authorization\Model\Acl\Loader\Rule::ACL_RULE_CACHE_KEY) + ->will( + $this->returnValue( + json_encode( + [ + ['role_id' => 1, 'resource_id' => 'Magento_Backend::all', 'permission' => 'allow'], + ['role_id' => 2, 'resource_id' => 1, 'permission' => 'allow'], + ['role_id' => 3, 'resource_id' => 1, 'permission' => 'deny'], + ] + ) + ) + ); $aclMock = $this->getMock(\Magento\Framework\Acl::class); $aclMock->expects($this->any())->method('has')->will($this->returnValue(true)); diff --git a/app/code/Magento/Authorization/Test/Unit/Model/CompositeUserContextTest.php b/app/code/Magento/Authorization/Test/Unit/Model/CompositeUserContextTest.php index 3724a97a08cd2..d899194bc9e95 100644 --- a/app/code/Magento/Authorization/Test/Unit/Model/CompositeUserContextTest.php +++ b/app/code/Magento/Authorization/Test/Unit/Model/CompositeUserContextTest.php @@ -1,6 +1,6 @@ contextMock = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\Context::class) + ->disableOriginalConstructor() + ->setMethods(['getResources']) + ->getMock(); + + $this->resourceConnectionMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection', 'getTableName']) + ->getMock(); + + $this->contextMock->expects($this->once()) + ->method('getResources') + ->will($this->returnValue($this->resourceConnectionMock)); + + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with('connection') + ->will($this->returnValue($this->connectionMock)); + + $this->resourceConnectionMock->expects($this->any()) + ->method('getTableName') + ->with('authorization_rule', 'connection') + ->will($this->returnArgument(0)); + + $this->aclBuilderMock = $this->getMockBuilder(\Magento\Framework\Acl\Builder::class) + ->disableOriginalConstructor() + ->setMethods(['getConfigCache']) + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->rootResourceMock = $this->getMockBuilder(\Magento\Framework\Acl\RootResource::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->aclCacheMock = $this->getMockBuilder(\Magento\Framework\Acl\CacheInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->aclDataCacheMock = $this->getMockBuilder(\Magento\Framework\Acl\Data\CacheInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->aclBuilderMock->expects($this->any()) + ->method('getConfigCache') + ->will($this->returnValue($this->aclDataCacheMock)); + + $this->ruleMock = $this->getMockBuilder(\Magento\Authorization\Model\Rules::class) + ->disableOriginalConstructor() + ->setMethods(['getRoleId']) + ->getMock(); + + $this->ruleMock->expects($this->any()) + ->method('getRoleId') + ->will($this->returnValue(self::TEST_ROLE_ID)); + + $this->model = new \Magento\Authorization\Model\ResourceModel\Rules( + $this->contextMock, + $this->aclBuilderMock, + $this->loggerMock, + $this->rootResourceMock, + $this->aclCacheMock, + 'connection', + $this->aclDataCacheMock + ); + } + + /** + * Test save with no resources posted. + */ + public function testSaveRelNoResources() + { + $this->connectionMock->expects($this->once()) + ->method('beginTransaction'); + $this->connectionMock->expects($this->once()) + ->method('delete') + ->with('authorization_rule', ['role_id = ?' => self::TEST_ROLE_ID]); + $this->connectionMock->expects($this->once()) + ->method('commit'); + + $this->aclDataCacheMock->expects($this->once()) + ->method('clean'); + + $this->model->saveRel($this->ruleMock); + } + + /** + * Test LocalizedException throw case. + * + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage TestException + */ + public function testLocalizedExceptionOccurance() + { + $exceptionPhrase = $this->getMockBuilder(\Magento\Framework\Phrase::class) + ->disableOriginalConstructor() + ->setMethods(['render']) + ->getMock(); + + $exceptionPhrase->expects($this->any())->method('render')->will($this->returnValue('TestException')); + + $exception = new \Magento\Framework\Exception\LocalizedException($exceptionPhrase); + + $this->connectionMock->expects($this->once()) + ->method('beginTransaction'); + + $this->connectionMock->expects($this->once()) + ->method('delete') + ->with('authorization_rule', ['role_id = ?' => self::TEST_ROLE_ID]) + ->will($this->throwException($exception)); + + $this->connectionMock->expects($this->once())->method('rollBack'); + + $this->model->saveRel($this->ruleMock); + } + + /** + * Test generic exception throw case. + */ + public function testGenericExceptionOccurance() + { + $exception = new \Exception('GenericException'); + + $this->connectionMock->expects($this->once()) + ->method('beginTransaction'); + + $this->connectionMock->expects($this->once()) + ->method('delete') + ->with('authorization_rule', ['role_id = ?' => self::TEST_ROLE_ID]) + ->will($this->throwException($exception)); + + $this->connectionMock->expects($this->once())->method('rollBack'); + $this->loggerMock->expects($this->once())->method('critical')->with($exception); + + $this->model->saveRel($this->ruleMock); + } +} diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index 0ca367d4854df..af88e8376dc75 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-authorization", "description": "Authorization module provides access to Magento ACL functionality.", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Authorization/etc/di.xml b/app/code/Magento/Authorization/etc/di.xml index 1159fb0a7606e..9370e14d81a35 100644 --- a/app/code/Magento/Authorization/etc/di.xml +++ b/app/code/Magento/Authorization/etc/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorization/etc/module.xml b/app/code/Magento/Authorization/etc/module.xml index f3fa8793f3e57..f27253870499d 100644 --- a/app/code/Magento/Authorization/etc/module.xml +++ b/app/code/Magento/Authorization/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorization/registration.php b/app/code/Magento/Authorization/registration.php index 6aabf5a16bb91..f35b17a4d6710 100644 --- a/app/code/Magento/Authorization/registration.php +++ b/app/code/Magento/Authorization/registration.php @@ -1,6 +1,6 @@ dataFactory->create($area); $params = []; - $data = $this->getRequest()->getPostValue(); + $data = $this->getRequest()->getParams(); + /* @var $paymentMethod \Magento\Authorizenet\Model\DirectPost */ $paymentMethod = $this->_objectManager->create(\Magento\Authorizenet\Model\Directpost::class); @@ -110,9 +111,8 @@ protected function _responseAction($area = 'frontend') $params['redirect'] = $helper->getRedirectIframeUrl($result); } + //registering parameter for iframe content $this->_coreRegistry->register(Iframe::REGISTRY_KEY, $params); - $this->_view->addPageLayoutHandles(); - $this->_view->loadLayout(false)->renderLayout(); } /** diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php index c0ac07a11adb3..6e2401d930b16 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php @@ -1,7 +1,7 @@ _responseAction('adminhtml'); + return $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE); } } diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php index 2816a3bae78b9..bc327caffb262 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php @@ -1,6 +1,6 @@ eventManager = $context->getEventManager(); $this->cartManagement = $cartManagement; $this->onepageCheckout = $onepageCheckout; $this->jsonHelper = $jsonHelper; + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); parent::__construct($context, $coreRegistry, $dataFactory); } @@ -127,9 +139,11 @@ protected function placeCheckoutOrder() ] ); } catch (LocalizedException $exception) { + $this->logger->critical($exception); $result->setData('error', true); $result->setData('error_messages', $exception->getMessage()); } catch (\Exception $exception) { + $this->logger->critical($exception); $result->setData('error', true); $result->setData( 'error_messages', diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Redirect.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Redirect.php index 8745737a19bd6..84d91dc89f155 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Redirect.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Redirect.php @@ -1,7 +1,7 @@ _responseAction('frontend'); + return $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE); } } diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/ReturnQuote.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/ReturnQuote.php index 5bfe916160967..c542c16e9fa7e 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/ReturnQuote.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/ReturnQuote.php @@ -1,7 +1,7 @@ setXCity($billing->getCity()) ->setXState($billing->getRegion()) ->setXZip($billing->getPostcode()) - ->setXCountry($billing->getCountry()) + ->setXCountry($billing->getCountryId()) ->setXPhone($billing->getTelephone()) ->setXFax($billing->getFax()) ->setXCustId($order->getCustomerId()) @@ -352,7 +352,7 @@ protected function buildRequest(\Magento\Framework\DataObject $payment) ->setXShipToCity($shipping->getCity()) ->setXShipToState($shipping->getRegion()) ->setXShipToZip($shipping->getPostcode()) - ->setXShipToCountry($shipping->getCountry()); + ->setXShipToCountry($shipping->getCountryId()); } $request->setXPoNum($payment->getPoNumber()) diff --git a/app/code/Magento/Authorizenet/Model/Debug.php b/app/code/Magento/Authorizenet/Model/Debug.php index 3e46731197660..7605aa762a183 100644 --- a/app/code/Magento/Authorizenet/Model/Debug.php +++ b/app/code/Magento/Authorizenet/Model/Debug.php @@ -1,6 +1,6 @@ setXCity(strval($billing->getCity())) ->setXState(strval($billing->getRegion())) ->setXZip(strval($billing->getPostcode())) - ->setXCountry(strval($billing->getCountry())) + ->setXCountry(strval($billing->getCountryId())) ->setXPhone(strval($billing->getTelephone())) ->setXFax(strval($billing->getFax())) ->setXCustId(strval($billing->getCustomerId())) @@ -151,7 +151,7 @@ public function setDataFromOrder( )->setXShipToZip( strval($shipping->getPostcode()) )->setXShipToCountry( - strval($shipping->getCountry()) + strval($shipping->getCountryId()) ); } diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php b/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php index 7ef6647285616..d0f6ea6515d11 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php @@ -1,6 +1,6 @@ getMockBuilder(\Magento\Framework\App\Response\Http::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->loggerMock = $this + ->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->getMock(); $this->objectManager = new ObjectManager($this); $this->placeOrderController = $this->objectManager->getObject( @@ -165,6 +174,7 @@ protected function setUp() 'cartManagement' => $this->cartManagementMock, 'onepageCheckout' => $this->onepageCheckout, 'jsonHelper' => $this->jsonHelperMock, + 'logger' => $this->loggerMock, ] ); } @@ -214,13 +224,15 @@ public function testExecute( * @param $controller * @param $quoteId * @param $result + * @param \Exception $exception Exception to check * @dataProvider textExecuteFailedPlaceOrderDataProvider */ public function testExecuteFailedPlaceOrder( $paymentMethod, $controller, $quoteId, - $result + $result, + $exception ) { $this->requestMock->expects($this->at(0)) ->method('getParam') @@ -238,7 +250,11 @@ public function testExecuteFailedPlaceOrder( $this->cartManagementMock->expects($this->once()) ->method('placeOrder') - ->willThrowException(new \Exception()); + ->willThrowException($exception); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); $this->jsonHelperMock->expects($this->any()) ->method('jsonEncode') @@ -278,11 +294,19 @@ public function textExecuteDataProvider() */ public function textExecuteFailedPlaceOrderDataProvider() { - $objectFailed = new \Magento\Framework\DataObject(); - $objectFailed->setData('error', true); - $objectFailed->setData( - 'error_messages', - __('An error occurred on the server. Please try to place the order again.') + $objectFailed1 = new \Magento\Framework\DataObject( + [ + 'error' => true, + 'error_messages' => __('An error occurred on the server. Please try to place the order again.') + ] + ); + $generalException = new \Exception('Exception logging will save the world!'); + $localizedException = new LocalizedException(__('Electronic payments save the trees.')); + $objectFailed2 = new \Magento\Framework\DataObject( + [ + 'error' => true, + 'error_messages' => $localizedException->getMessage() + ] ); return [ @@ -290,7 +314,15 @@ public function textExecuteFailedPlaceOrderDataProvider() ['method' => 'authorizenet_directpost'], IframeConfigProvider::CHECKOUT_IDENTIFIER, 1, - $objectFailed + $objectFailed1, + $generalException, + ], + [ + ['method' => 'authorizenet_directpost'], + IframeConfigProvider::CHECKOUT_IDENTIFIER, + 1, + $objectFailed2, + $localizedException, ], ]; } diff --git a/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/RedirectTest.php b/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/RedirectTest.php index d11a7b8df3521..4ec5e401c9e96 100644 --- a/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/RedirectTest.php +++ b/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/RedirectTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/events.xml b/app/code/Magento/Authorizenet/etc/adminhtml/events.xml index 4a669cd7d9cac..8114b8c8312cc 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/events.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/routes.xml b/app/code/Magento/Authorizenet/etc/adminhtml/routes.xml index ef3e63f2c9c90..028e1a8500d07 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/routes.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml index fd2fb84f0a290..3d4cde185dcd1 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml index 70b413b5c1a39..f5b053003f1f2 100644 --- a/app/code/Magento/Authorizenet/etc/config.xml +++ b/app/code/Magento/Authorizenet/etc/config.xml @@ -1,7 +1,7 @@ @@ -10,7 +10,7 @@ 0 - AE,VI,MC,DI + AE,VI,MC,DI,JCB,DN 0 0 diff --git a/app/code/Magento/Authorizenet/etc/di.xml b/app/code/Magento/Authorizenet/etc/di.xml index f5e595fb450e8..3bd70f25a3bf9 100644 --- a/app/code/Magento/Authorizenet/etc/di.xml +++ b/app/code/Magento/Authorizenet/etc/di.xml @@ -1,7 +1,7 @@ @@ -16,4 +16,14 @@ Magento\Authorizenet\Model\Directpost\Session\Storage + + + + 1 + 1 + 1 + 1 + + + diff --git a/app/code/Magento/Authorizenet/etc/frontend/di.xml b/app/code/Magento/Authorizenet/etc/frontend/di.xml index 7f20e067cc426..8dcf8ed700dcb 100644 --- a/app/code/Magento/Authorizenet/etc/frontend/di.xml +++ b/app/code/Magento/Authorizenet/etc/frontend/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/frontend/events.xml b/app/code/Magento/Authorizenet/etc/frontend/events.xml index bb11f4bca363a..2c6e3f12a9196 100644 --- a/app/code/Magento/Authorizenet/etc/frontend/events.xml +++ b/app/code/Magento/Authorizenet/etc/frontend/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/frontend/page_types.xml b/app/code/Magento/Authorizenet/etc/frontend/page_types.xml index 6dc50d5d28dd9..be4692b135955 100644 --- a/app/code/Magento/Authorizenet/etc/frontend/page_types.xml +++ b/app/code/Magento/Authorizenet/etc/frontend/page_types.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/frontend/routes.xml b/app/code/Magento/Authorizenet/etc/frontend/routes.xml index 7da347a2e6381..1bdcff9f1efe1 100644 --- a/app/code/Magento/Authorizenet/etc/frontend/routes.xml +++ b/app/code/Magento/Authorizenet/etc/frontend/routes.xml @@ -1,7 +1,7 @@ @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Authorizenet/etc/frontend/sections.xml b/app/code/Magento/Authorizenet/etc/frontend/sections.xml index 33c48e60e8dab..977a4b14e3e14 100644 --- a/app/code/Magento/Authorizenet/etc/frontend/sections.xml +++ b/app/code/Magento/Authorizenet/etc/frontend/sections.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/etc/module.xml b/app/code/Magento/Authorizenet/etc/module.xml index 75317e254efaa..91d93e56e0cad 100644 --- a/app/code/Magento/Authorizenet/etc/module.xml +++ b/app/code/Magento/Authorizenet/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/i18n/en_US.csv b/app/code/Magento/Authorizenet/i18n/en_US.csv index 7183c706dc0a2..45025304c4e44 100644 --- a/app/code/Magento/Authorizenet/i18n/en_US.csv +++ b/app/code/Magento/Authorizenet/i18n/en_US.csv @@ -64,3 +64,4 @@ Debug,Debug "Minimum Order Total","Minimum Order Total" "Maximum Order Total","Maximum Order Total" "Sort Order","Sort Order" +"Sorry, but something went wrong. Please contact the seller.","Sorry, but something went wrong. Please contact the seller." diff --git a/app/code/Magento/Authorizenet/registration.php b/app/code/Magento/Authorizenet/registration.php index a0eab4323313e..ad98beafa744d 100644 --- a/app/code/Magento/Authorizenet/registration.php +++ b/app/code/Magento/Authorizenet/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_index.xml b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_index.xml index aeed400c3c8fa..851cbf398750d 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_index.xml +++ b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_index.xml @@ -1,7 +1,7 @@ @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml index 21e8f1d70fbb5..ec5ce845b8521 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml +++ b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml @@ -1,7 +1,7 @@ @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_view.xml b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_view.xml index d81573c891c9f..7470afcfdb7d0 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_view.xml +++ b/app/code/Magento/Authorizenet/view/adminhtml/layout/sales_order_view.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml index ae5a5ec6217ed..2b95af46a6b47 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml +++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml @@ -1,6 +1,6 @@ getInfoData('cc_exp_year');
    +
    -
    -
    + +
    @@ -66,7 +76,11 @@ $ccExpYear = $block->getInfoData('cc_exp_year');
    + hasVerification()): ?> -
    -
    - \ No newline at end of file + diff --git a/app/code/Magento/Authorizenet/view/adminhtml/web/js/direct-post.js b/app/code/Magento/Authorizenet/view/adminhtml/web/js/direct-post.js index 69dd63cf47caa..b9bb218d74190 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/web/js/direct-post.js +++ b/app/code/Magento/Authorizenet/view/adminhtml/web/js/direct-post.js @@ -1,7 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + (function (factory) { if (typeof define === 'function' && define.amd) { define([ diff --git a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_backendresponse.xml b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_backendresponse.xml index 97f425e78c7de..1eedd5e26a3ed 100644 --- a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_backendresponse.xml +++ b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_backendresponse.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_redirect.xml b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_redirect.xml index 97f425e78c7de..1eedd5e26a3ed 100644 --- a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_redirect.xml +++ b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_redirect.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_response.xml b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_response.xml index 97f425e78c7de..1eedd5e26a3ed 100644 --- a/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_response.xml +++ b/app/code/Magento/Authorizenet/view/frontend/layout/authorizenet_directpost_payment_response.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Authorizenet/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Authorizenet/view/frontend/layout/checkout_index_index.xml index 489dddc9b15d6..1025207fec1d5 100644 --- a/app/code/Magento/Authorizenet/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Authorizenet/view/frontend/layout/checkout_index_index.xml @@ -1,7 +1,7 @@ @@ -46,4 +46,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js index 4af97d5775a7f..26c0e732303d0 100644 --- a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js +++ b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/authorizenet.js b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/authorizenet.js index 83b3984735563..1ae78194b97a1 100644 --- a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/authorizenet.js +++ b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/authorizenet.js @@ -1,26 +1,22 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -/*browser:true*/ -/*global define*/ -define( - [ - 'uiComponent', - 'Magento_Checkout/js/model/payment/renderer-list' - ], - function ( - Component, - rendererList - ) { - 'use strict'; - rendererList.push( - { - type: 'authorizenet_directpost', - component: 'Magento_Authorizenet/js/view/payment/method-renderer/authorizenet-directpost' - } - ); - /** Add view logic here if needed */ - return Component.extend({}); - } -); \ No newline at end of file + +define([ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' +], +function (Component, rendererList) { + 'use strict'; + + rendererList.push( + { + type: 'authorizenet_directpost', + component: 'Magento_Authorizenet/js/view/payment/method-renderer/authorizenet-directpost' + } + ); + + /** Add view logic here if needed */ + return Component.extend({}); +}); diff --git a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js index cd05960c17633..989e24cada386 100644 --- a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js +++ b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js @@ -1,64 +1,64 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -define( - [ - 'jquery', - 'Magento_Payment/js/view/payment/iframe' - ], - function ($, Component) { - 'use strict'; - return Component.extend({ - defaults: { - template: 'Magento_Authorizenet/payment/authorizenet-directpost', - timeoutMessage: 'Sorry, but something went wrong. Please contact the seller.' - }, - placeOrderHandler: null, - validateHandler: null, +define([ + 'jquery', + 'Magento_Payment/js/view/payment/iframe', + 'mage/translate' +], +function ($, Component, $t) { + 'use strict'; - /** - * @param {Object} handler - */ - setPlaceOrderHandler: function (handler) { - this.placeOrderHandler = handler; - }, + return Component.extend({ + defaults: { + template: 'Magento_Authorizenet/payment/authorizenet-directpost', + timeoutMessage: $t('Sorry, but something went wrong. Please contact the seller.') + }, + placeOrderHandler: null, + validateHandler: null, - /** - * @param {Object} handler - */ - setValidateHandler: function (handler) { - this.validateHandler = handler; - }, + /** + * @param {Object} handler + */ + setPlaceOrderHandler: function (handler) { + this.placeOrderHandler = handler; + }, - /** - * @returns {Object} - */ - context: function () { - return this; - }, + /** + * @param {Object} handler + */ + setValidateHandler: function (handler) { + this.validateHandler = handler; + }, - /** - * @returns {Boolean} - */ - isShowLegend: function () { - return true; - }, + /** + * @returns {Object} + */ + context: function () { + return this; + }, - /** - * @returns {String} - */ - getCode: function () { - return 'authorizenet_directpost'; - }, + /** + * @returns {Boolean} + */ + isShowLegend: function () { + return true; + }, - /** - * @returns {Boolean} - */ - isActive: function () { - return true; - } - }); - } -); + /** + * @returns {String} + */ + getCode: function () { + return 'authorizenet_directpost'; + }, + + /** + * @returns {Boolean} + */ + isActive: function () { + return true; + } + }); +}); diff --git a/app/code/Magento/Authorizenet/view/frontend/web/template/payment/authorizenet-directpost.html b/app/code/Magento/Authorizenet/view/frontend/web/template/payment/authorizenet-directpost.html index 1aff94817c6eb..2de6cad54d267 100644 --- a/app/code/Magento/Authorizenet/view/frontend/web/template/payment/authorizenet-directpost.html +++ b/app/code/Magento/Authorizenet/view/frontend/web/template/payment/authorizenet-directpost.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/App/AbstractAction.php b/app/code/Magento/Backend/App/AbstractAction.php index f0973b8f66a65..4efd013f43a1c 100644 --- a/app/code/Magento/Backend/App/AbstractAction.php +++ b/app/code/Magento/Backend/App/AbstractAction.php @@ -1,6 +1,6 @@ _scopePool = $scopePool; + $this->appConfig = $appConfig; } /** - * Retrieve config value by path and scope - * - * @param string $path - * @return mixed + * @inheritdoc */ public function getValue($path) { - return $this->_scopePool->getScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null)->getValue($path); + if (isset($this->data[$path])) { + return $this->data[$path]; + } + + $configPath = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; + if ($path) { + $configPath .= '/' . $path; + } + return $this->appConfig->get(System::CONFIG_TYPE, $configPath); } /** - * Set config value in the corresponding config scope - * - * @param string $path - * @param mixed $value - * @return void + * @inheritdoc */ public function setValue($path, $value) { - $this->_scopePool->getScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null)->setValue($path, $value); + $this->data[$path] = $value; } /** - * Retrieve config flag - * - * @param string $path - * @return bool + * @inheritdoc */ public function isSetFlag($path) { - return !!$this->_scopePool->getScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null)->getValue($path); + $configPath = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; + if ($path) { + $configPath .= '/' . $path; + } + return (bool) $this->appConfig->get(System::CONFIG_TYPE, $configPath); } } diff --git a/app/code/Magento/Backend/App/ConfigInterface.php b/app/code/Magento/Backend/App/ConfigInterface.php index 4000b54cc9834..1e183bc8fd1bd 100644 --- a/app/code/Magento/Backend/App/ConfigInterface.php +++ b/app/code/Magento/Backend/App/ConfigInterface.php @@ -2,7 +2,7 @@ /** * Default application path for backend area * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\App; @@ -15,6 +15,8 @@ interface ConfigInterface /** * Retrieve config value by path * + * Path should looks like keys imploded by "/". For example scopes/stores/admin + * * @param string $path * @return mixed * @api @@ -24,6 +26,7 @@ public function getValue($path); /** * Set config value * + * @deprecated * @param string $path * @param mixed $value * @return void @@ -34,6 +37,8 @@ public function setValue($path, $value); /** * Retrieve config flag * + * Path should looks like keys imploded by "/". For example scopes/stores/admin + * * @param string $path * @return bool * @api diff --git a/app/code/Magento/Backend/App/DefaultPath.php b/app/code/Magento/Backend/App/DefaultPath.php index ef3d502db8ab8..9a25e328bde52 100644 --- a/app/code/Magento/Backend/App/DefaultPath.php +++ b/app/code/Magento/Backend/App/DefaultPath.php @@ -2,7 +2,7 @@ /** * Default application path for backend area * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\App; diff --git a/app/code/Magento/Backend/App/Request/PathInfoProcessor.php b/app/code/Magento/Backend/App/Request/PathInfoProcessor.php index 08e115e9ec795..e0890ce917897 100644 --- a/app/code/Magento/Backend/App/Request/PathInfoProcessor.php +++ b/app/code/Magento/Backend/App/Request/PathInfoProcessor.php @@ -2,7 +2,7 @@ /** * Prevents path info processing for admin store * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\App\Request; diff --git a/app/code/Magento/Backend/App/Response/Http/FileFactory.php b/app/code/Magento/Backend/App/Response/Http/FileFactory.php index dec52cdb646b0..1c67275a7fc67 100644 --- a/app/code/Magento/Backend/App/Response/Http/FileFactory.php +++ b/app/code/Magento/Backend/App/Response/Http/FileFactory.php @@ -1,6 +1,6 @@ menuItemChecker = $menuItemChecker; + $this->escaper = $escaper; + } + + /** + * Render menu item anchor. + * + * It is used in backend menu to render anchor menu. + * + * @param Item|false $activeItem Can be false if menu item is inaccessible + * but was triggered directly using controller. It is a legacy code behaviour. + * @param Item $menuItem + * @param int $level + * @return string + */ + public function renderAnchor($activeItem, Item $menuItem, $level) + { + if ($level == 1 && $menuItem->getUrl() == '#') { + $output = '' + . '' . $this->escaper->escapeHtml(__($menuItem->getTitle())) . '' + . ''; + } else { + $target = $menuItem->getTarget() ? ('target=' . $menuItem->getTarget()) : ''; + $output = '_renderItemAnchorTitle( + $menuItem + ) . $this->_renderItemOnclickFunction( + $menuItem + ) . ' class="' . ($this->menuItemChecker->isItemActive($activeItem, $menuItem, $level) ? '_active' : '') + . '">' . '' . $this->escaper->escapeHtml(__($menuItem->getTitle())) + . '' . ''; + } + + return $output; + } + + /** + * Render menu item anchor title + * + * @param Item $menuItem + * @return string + */ + private function _renderItemAnchorTitle($menuItem) + { + return $menuItem->hasTooltip() ? 'title="' . __($menuItem->getTooltip()) . '"' : ''; + } + + /** + * Render menu item onclick function + * + * @param Item $menuItem + * @return string + */ + private function _renderItemOnclickFunction($menuItem) + { + return $menuItem->hasClickCallback() ? ' onclick="' . $menuItem->getClickCallback() . '"' : ''; + } +} diff --git a/app/code/Magento/Backend/Block/Cache.php b/app/code/Magento/Backend/Block/Cache.php index 8b53882274182..b9a11fa5bb543 100644 --- a/app/code/Magento/Backend/Block/Cache.php +++ b/app/code/Magento/Backend/Block/Cache.php @@ -1,6 +1,6 @@ state = $state; + } + + /** + * {@inheritdoc} + */ + public function isVisible() + { + return $this->state->getMode() !== State::MODE_PRODUCTION; + } +} diff --git a/app/code/Magento/Backend/Block/Catalog/Product/Tab/Container.php b/app/code/Magento/Backend/Block/Catalog/Product/Tab/Container.php index bed5ee24434d9..49107a0ae4f7f 100644 --- a/app/code/Magento/Backend/Block/Catalog/Product/Tab/Container.php +++ b/app/code/Magento/Backend/Block/Catalog/Product/Tab/Container.php @@ -1,6 +1,6 @@ setCollection($collection); + parent::_prepareCollection(); + + /** @var Product $product */ + foreach ($collection as $product) { + $product->setPrice($product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue()); + } - return parent::_prepareCollection(); + return $this; } /** diff --git a/app/code/Magento/Backend/Block/Dashboard/Totals.php b/app/code/Magento/Backend/Block/Dashboard/Totals.php index 342bece99299f..0e44a793c3422 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Totals.php +++ b/app/code/Magento/Backend/Block/Dashboard/Totals.php @@ -1,6 +1,6 @@ _url = $url; $this->_iteratorFactory = $iteratorFactory; $this->_authSession = $authSession; $this->_menuConfig = $menuConfig; $this->_localeResolver = $localeResolver; + $this->menuItemChecker = $menuItemChecker; + $this->anchorRenderer = $anchorRenderer; parent::__construct($context, $data); } @@ -99,30 +115,6 @@ protected function _construct() $this->setCacheTags([self::CACHE_TAGS]); } - /** - * Check whether given item is currently selected - * - * @param \Magento\Backend\Model\Menu\Item $item - * @param int $level - * @return bool - */ - protected function _isItemActive(\Magento\Backend\Model\Menu\Item $item, $level) - { - $itemModel = $this->getActiveItemModel(); - $output = false; - - if ($level == 0 && - $itemModel instanceof \Magento\Backend\Model\Menu\Item && - ($itemModel->getId() == $item->getId() || - $item->getChildren()->get( - $itemModel->getId() - ) !== null) - ) { - $output = true; - } - return $output; - } - /** * Render menu item anchor label * @@ -134,40 +126,6 @@ protected function _getAnchorLabel($menuItem) return $this->escapeHtml(__($menuItem->getTitle())); } - /** - * Render menu item anchor title - * - * @param \Magento\Backend\Model\Menu\Item $menuItem - * @return string - */ - protected function _renderItemAnchorTitle($menuItem) - { - return $menuItem->hasTooltip() ? 'title="' . __($menuItem->getTooltip()) . '"' : ''; - } - - /** - * Render menu item onclick function - * - * @param \Magento\Backend\Model\Menu\Item $menuItem - * @return string - */ - protected function _renderItemOnclickFunction($menuItem) - { - return $menuItem->hasClickCallback() ? ' onclick="' . $menuItem->getClickCallback() . '"' : ''; - } - - /** - * Render menu item anchor css class - * - * @param \Magento\Backend\Model\Menu\Item $menuItem - * @param int $level - * @return string - */ - protected function _renderAnchorCssClass($menuItem, $level) - { - return $this->_isItemActive($menuItem, $level) ? '_active' : ''; - } - /** * Render menu item mouse events * @param \Magento\Backend\Model\Menu\Item $menuItem @@ -188,10 +146,11 @@ protected function _renderMouseEvent($menuItem) protected function _renderItemCssClass($menuItem, $level) { $isLast = 0 == $level && (bool)$this->getMenuModel()->isLast($menuItem) ? 'last' : ''; - $output = ($this->_isItemActive( - $menuItem, - $level - ) ? '_current _active' : '') . + $output = ($this->menuItemChecker->isItemActive( + $this->getActiveItemModel(), + $menuItem, + $level + ) ? '_current _active' : '') . ' ' . ($menuItem->hasChildren() ? 'parent' : '') . ' ' . @@ -202,34 +161,6 @@ protected function _renderItemCssClass($menuItem, $level) return $output; } - /** - * Render menu item anchor - * @param \Magento\Backend\Model\Menu\Item $menuItem - * @param int $level - * @return string - */ - protected function _renderAnchor($menuItem, $level) - { - if ($level == 1 && $menuItem->getUrl() == '#') { - $output = '' - . '' . $this->_getAnchorLabel($menuItem) . '' - . ''; - } else { - $output = '_renderItemAnchorTitle( - $menuItem - ) . $this->_renderItemOnclickFunction( - $menuItem - ) . ' class="' . $this->_renderAnchorCssClass( - $menuItem, - $level - ) . '">' . '' . $this->_getAnchorLabel( - $menuItem - ) . '' . ''; - } - - return $output; - } - /** * Get menu filter iterator * @@ -336,7 +267,7 @@ public function renderMenu($menu, $level = 0) $menuItem->getId() ) . 'role="menuitem">'; - $output .= $this->_renderAnchor($menuItem, $level); + $output .= $this->anchorRenderer->renderAnchor($this->getActiveItemModel(), $menuItem, $level); if ($menuItem->hasChildren()) { $output .= $this->renderMenu($menuItem->getChildren(), $level + 1); @@ -456,7 +387,7 @@ public function renderNavigation($menu, $level = 0, $limit = 0, $colBrakes = []) $id = $this->getJsId($menuItem->getId()); $subMenu = $this->_addSubMenu($menuItem, $level, $limit, $id); - $anchor = $this->_renderAnchor($menuItem, $level); + $anchor = $this->anchorRenderer->renderAnchor($this->getActiveItemModel(), $menuItem, $level); $output .= '
  • getUiId($menuItem->getId()) . ' class="item-' . $itemClass . ' ' . $this->_renderItemCssClass($menuItem, $level) . ($level == 0 ? '" id="' . $id . '" aria-haspopup="true' : '') @@ -474,7 +405,7 @@ public function renderNavigation($menu, $level = 0, $limit = 0, $colBrakes = []) /** * Get current selected menu item * - * @return \Magento\Backend\Model\Menu\Item|null|bool + * @return \Magento\Backend\Model\Menu\Item|false */ public function getActiveItemModel() { diff --git a/app/code/Magento/Backend/Block/MenuItemChecker.php b/app/code/Magento/Backend/Block/MenuItemChecker.php new file mode 100644 index 0000000000000..b85b060cd379d --- /dev/null +++ b/app/code/Magento/Backend/Block/MenuItemChecker.php @@ -0,0 +1,49 @@ +isActiveItemEqualOrChild($activeItem, $item) + ) { + $output = true; + } + return $output; + } + + /** + * @param Item $activeItem, + * @param Item $item + * @return bool + */ + private function isActiveItemEqualOrChild($activeItem, $item) + { + return ($activeItem->getId() == $item->getId()) + || ($item->getChildren()->get($activeItem->getId()) !== null); + } +} diff --git a/app/code/Magento/Backend/Block/Page.php b/app/code/Magento/Backend/Block/Page.php index dca6a97a588c1..750eaebf58de6 100644 --- a/app/code/Magento/Backend/Block/Page.php +++ b/app/code/Magento/Backend/Block/Page.php @@ -1,6 +1,6 @@ getModuleName(); - } - - return !$this->_scopeConfig->isSetFlag( - 'advanced/modules_disable_output/' . $moduleName, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + return true; } /** diff --git a/app/code/Magento/Backend/Block/Template/Context.php b/app/code/Magento/Backend/Block/Template/Context.php index 2cebd1cd5e49e..4f1324763f351 100644 --- a/app/code/Magento/Backend/Block/Template/Context.php +++ b/app/code/Magento/Backend/Block/Template/Context.php @@ -2,7 +2,7 @@ /** * Backend block template context * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Backend/Block/Text/ListText.php b/app/code/Magento/Backend/Block/Text/ListText.php index 3ec68b25707b3..0dd8d0c05ac53 100644 --- a/app/code/Magento/Backend/Block/Text/ListText.php +++ b/app/code/Magento/Backend/Block/Text/ListText.php @@ -1,6 +1,6 @@ */ -namespace Magento\Backend\Block\Widget\Grid\Column\Renderer; - class Text extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** @@ -21,30 +20,53 @@ class Text extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRe protected $_variablePattern = '/\\$([a-z0-9_]+)/i'; /** - * Renders grid column + * Get value for the cel * - * @param \Magento\Framework\DataObject $row - * @return mixed + * @param DataObject $row + * @return string */ - public function _getValue(\Magento\Framework\DataObject $row) + public function _getValue(DataObject $row) { - $format = $this->getColumn()->getFormat() ? $this->getColumn()->getFormat() : null; - $defaultValue = $this->getColumn()->getDefault(); - if ($format === null) { - // If no format and it column not filtered specified return data as is. - $data = parent::_getValue($row); - $string = $data === null ? $defaultValue : $data; - return $this->escapeHtml($string); - } elseif (preg_match_all($this->_variablePattern, $format, $matches)) { - // Parsing of format string - $formattedString = $format; - foreach ($matches[0] as $matchIndex => $match) { - $value = $row->getData($matches[1][$matchIndex]); - $formattedString = str_replace($match, $value, $formattedString); + if (null === $this->getColumn()->getFormat()) { + return $this->getSimpleValue($row); + } + return $this->getFormattedValue($row); + } + + /** + * Get simple value + * + * @param DataObject $row + * @return string + */ + private function getSimpleValue(DataObject $row) + { + $data = parent::_getValue($row); + $value = null === $data ? $this->getColumn()->getDefault() : $data; + if (true === $this->getColumn()->getTranslate()) { + $value = __($value); + } + return $this->escapeHtml($value); + } + + /** + * Replace placeholders in the string with values + * + * @param DataObject $row + * @return string + */ + private function getFormattedValue(DataObject $row) + { + $value = $this->getColumn()->getFormat() ?: null; + if (true === $this->getColumn()->getTranslate()) { + $value = __($value); + } + if (preg_match_all($this->_variablePattern, $value, $matches)) { + foreach ($matches[0] as $index => $match) { + $replacement = $row->getData($matches[1][$index]); + $value = str_replace($match, $replacement, $value); } - return $formattedString; - } else { - return $this->escapeHtml($format); } + return $this->escapeHtml($value); } } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Wrapline.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Wrapline.php index a45584438af5e..b11d14be78f98 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Wrapline.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Wrapline.php @@ -1,6 +1,6 @@ */ -namespace Magento\Backend\Block\Widget\Grid; - class Massaction extends \Magento\Backend\Block\Widget\Grid\Massaction\AbstractMassaction { } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php index 2f3df1377b395..380b2991eb0d9 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -1,18 +1,18 @@ */ abstract class AbstractMassaction extends \Magento\Backend\Block\Widget { @@ -73,20 +73,21 @@ protected function _construct() * 'complete' => string, // Only for ajax enabled grid (optional) * 'url' => string, * 'confirm' => string, // text of confirmation of this action (optional) - * 'additional' => string // (optional) + * 'additional' => string, // (optional) + * 'visible' => object // instance of VisibilityCheckerInterface (optional) * ); * * @param string $itemId - * @param array|\Magento\Framework\DataObject $item + * @param array|DataObject $item * @return $this */ public function addItem($itemId, $item) { if (is_array($item)) { - $item = new \Magento\Framework\DataObject($item); + $item = new DataObject($item); } - if ($item instanceof \Magento\Framework\DataObject) { + if ($item instanceof DataObject && $this->isVisible($item)) { $item->setId($itemId); $item->setUrl($this->getUrl($item->getUrl())); $this->_items[$itemId] = $item; @@ -95,6 +96,19 @@ public function addItem($itemId, $item) return $this; } + /** + * Check that item can be added to list + * + * @param DataObject $item + * @return bool + */ + private function isVisible(DataObject $item) + { + /** @var VisibilityChecker $checker */ + $checker = $item->getData('visible'); + return (!$checker instanceof VisibilityChecker) || $checker->isVisible(); + } + /** * Retrieve massaction item with id $itemId * diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Additional.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Additional.php index 898e2efb508bd..15250b3904d2d 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Additional.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Additional.php @@ -1,6 +1,6 @@ getState()->getMode() === State::MODE_PRODUCTION) { + $this->messageManager->addErrorMessage(__('You can\'t change status of cache type(s) in production mode')); + } else { + $this->disableCache(); + } + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('adminhtml/*'); + } + + /** + * Disable cache + * + * @return void + */ + private function disableCache() { try { $types = $this->getRequest()->getParam('types'); @@ -41,9 +67,20 @@ public function execute() } catch (\Exception $e) { $this->messageManager->addException($e, __('An error occurred while disabling cache.')); } + } + + /** + * Get State Instance + * + * @return State + * @deprecated + */ + private function getState() + { + if ($this->state === null) { + $this->state = ObjectManager::getInstance()->get(State::class); + } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - return $resultRedirect->setPath('adminhtml/*'); + return $this->state; } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php index 6c8bccfee166a..0cf09508c2932 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php @@ -1,22 +1,48 @@ getState()->getMode() === State::MODE_PRODUCTION) { + $this->messageManager->addErrorMessage(__('You can\'t change status of cache type(s) in production mode')); + } else { + $this->enableCache(); + } + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('adminhtml/*'); + } + + /** + * Enable cache + * + * @return void + */ + private function enableCache() { try { $types = $this->getRequest()->getParam('types'); @@ -40,9 +66,20 @@ public function execute() } catch (\Exception $e) { $this->messageManager->addException($e, __('An error occurred while enabling cache.')); } + } + + /** + * Get State Instance + * + * @return State + * @deprecated + */ + private function getState() + { + if ($this->state === null) { + $this->state = ObjectManager::getInstance()->get(State::class); + } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - return $resultRedirect->setPath('adminhtml/*'); + return $this->state; } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php index 83ea91e32bf8c..ed546ba91adec 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php @@ -1,7 +1,7 @@ resultPageFactory->create(); + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->setActiveMenu('Magento_Backend::system_store'); + $resultPage->addBreadcrumb(__('Stores'), __('Stores')); + $resultPage->addBreadcrumb(__('All Stores'), __('All Stores')); $resultPage->getConfig()->getTitle()->prepend(__('Stores')); - return $resultPage; } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/NewGroup.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/NewGroup.php index c9d46a1a779b4..40dd82dd8cd4b 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/NewGroup.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/NewGroup.php @@ -1,7 +1,7 @@ filterManager->removeTags($postData['website']['name']); + $websiteModel = $this->_objectManager->create(\Magento\Store\Model\Website::class); + if ($postData['website']['website_id']) { + $websiteModel->load($postData['website']['website_id']); + } + $websiteModel->setData($postData['website']); + if ($postData['website']['website_id'] == '') { + $websiteModel->setId(null); + } + + $websiteModel->save(); + $this->messageManager->addSuccess(__('You saved the website.')); + + return $postData; + } + + /** + * Process Store model save + * + * @param array $postData + * @throws \Magento\Framework\Exception\LocalizedException + * @return array + */ + private function processStoreSave($postData) + { + $eventName = 'store_edit'; + /** @var \Magento\Store\Model\Store $storeModel */ + $storeModel = $this->_objectManager->create(\Magento\Store\Model\Store::class); + $postData['store']['name'] = $this->filterManager->removeTags($postData['store']['name']); + if ($postData['store']['store_id']) { + $storeModel->load($postData['store']['store_id']); + } + $storeModel->setData($postData['store']); + if ($postData['store']['store_id'] == '') { + $storeModel->setId(null); + $eventName = 'store_add'; + } + $groupModel = $this->_objectManager->create( + \Magento\Store\Model\Group::class + )->load( + $storeModel->getGroupId() + ); + $storeModel->setWebsiteId($groupModel->getWebsiteId()); + if (!$storeModel->isActive() && $storeModel->isDefault()) { + throw new \Magento\Framework\Exception\LocalizedException( + __('The default store cannot be disabled') + ); + } + $storeModel->save(); + $this->_objectManager->get(\Magento\Store\Model\StoreManager::class)->reinitStores(); + $this->_eventManager->dispatch($eventName, ['store' => $storeModel]); + $this->messageManager->addSuccess(__('You saved the store view.')); + + return $postData; + } + + /** + * Process StoreGroup model save + * + * @param array $postData + * @throws \Magento\Framework\Exception\LocalizedException + * @return array + */ + private function processGroupSave($postData) + { + $postData['group']['name'] = $this->filterManager->removeTags($postData['group']['name']); + /** @var \Magento\Store\Model\Group $groupModel */ + $groupModel = $this->_objectManager->create(\Magento\Store\Model\Group::class); + if ($postData['group']['group_id']) { + $groupModel->load($postData['group']['group_id']); + } + $groupModel->setData($postData['group']); + if ($postData['group']['group_id'] == '') { + $groupModel->setId(null); + } + if (!$this->isSelectedDefaultStoreActive($postData, $groupModel)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('An inactive store view cannot be saved as default store view') + ); + } + $groupModel->save(); + $this->_eventManager->dispatch('store_group_save', ['group' => $groupModel]); + $this->messageManager->addSuccess(__('You saved the store.')); + + return $postData; + } + /** * @return \Magento\Backend\Model\View\Result\Redirect * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -22,73 +121,16 @@ public function execute() $redirectResult->setPath('adminhtml/*/'); return $redirectResult; } - try { switch ($postData['store_type']) { case 'website': - $postData['website']['name'] = $this->filterManager->removeTags($postData['website']['name']); - $websiteModel = $this->_objectManager->create(\Magento\Store\Model\Website::class); - if ($postData['website']['website_id']) { - $websiteModel->load($postData['website']['website_id']); - } - $websiteModel->setData($postData['website']); - if ($postData['website']['website_id'] == '') { - $websiteModel->setId(null); - } - - $websiteModel->save(); - $this->messageManager->addSuccess(__('You saved the website.')); + $postData = $this->processWebsiteSave($postData); break; - case 'group': - $postData['group']['name'] = $this->filterManager->removeTags($postData['group']['name']); - /** @var \Magento\Store\Model\Group $groupModel */ - $groupModel = $this->_objectManager->create(\Magento\Store\Model\Group::class); - if ($postData['group']['group_id']) { - $groupModel->load($postData['group']['group_id']); - } - $groupModel->setData($postData['group']); - if ($postData['group']['group_id'] == '') { - $groupModel->setId(null); - } - if (!$this->isSelectedDefaultStoreActive($postData, $groupModel)) { - throw new \Magento\Framework\Exception\LocalizedException( - __('An inactive store view cannot be saved as default store view') - ); - } - $groupModel->save(); - $this->_eventManager->dispatch('store_group_save', ['group' => $groupModel]); - $this->messageManager->addSuccess(__('You saved the store.')); + $postData = $this->processGroupSave($postData); break; - case 'store': - $eventName = 'store_edit'; - /** @var \Magento\Store\Model\Store $storeModel */ - $storeModel = $this->_objectManager->create(\Magento\Store\Model\Store::class); - $postData['store']['name'] = $this->filterManager->removeTags($postData['store']['name']); - if ($postData['store']['store_id']) { - $storeModel->load($postData['store']['store_id']); - } - $storeModel->setData($postData['store']); - if ($postData['store']['store_id'] == '') { - $storeModel->setId(null); - $eventName = 'store_add'; - } - $groupModel = $this->_objectManager->create( - \Magento\Store\Model\Group::class - )->load( - $storeModel->getGroupId() - ); - $storeModel->setWebsiteId($groupModel->getWebsiteId()); - if (!$storeModel->isActive() && $storeModel->isDefault()) { - throw new \Magento\Framework\Exception\LocalizedException( - __('The default store cannot be disabled') - ); - } - $storeModel->save(); - $this->_objectManager->get(\Magento\Store\Model\StoreManager::class)->reinitStores(); - $this->_eventManager->dispatch($eventName, ['store' => $storeModel]); - $this->messageManager->addSuccess(__('You saved the store view.')); + $postData = $this->processStoreSave($postData); break; default: $redirectResult->setPath('adminhtml/*/'); diff --git a/app/code/Magento/Backend/Cron/CleanCache.php b/app/code/Magento/Backend/Cron/CleanCache.php index 840e0dcfcfde0..7dc09da13d608 100644 --- a/app/code/Magento/Backend/Cron/CleanCache.php +++ b/app/code/Magento/Backend/Cron/CleanCache.php @@ -1,6 +1,6 @@ _path = $pathInMenuStructure . '/'; } $this->_logger = $logger; $this->setIteratorClass(\Magento\Backend\Model\Menu\Iterator::class); + $this->menuItemFactory = $menuItemFactory ?: ObjectManager::getInstance() + ->create(Factory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance()->create(SerializerInterface::class); } /** * Add child to menu item * - * @param \Magento\Backend\Model\Menu\Item $item + * @param Item $item * @param string $parentId * @param int $index * @return void * @throws \InvalidArgumentException */ - public function add(\Magento\Backend\Model\Menu\Item $item, $parentId = null, $index = null) + public function add(Item $item, $parentId = null, $index = null) { if ($parentId !== null) { $parentItem = $this->get($parentId); @@ -69,13 +96,13 @@ public function add(\Magento\Backend\Model\Menu\Item $item, $parentId = null, $i * Retrieve menu item by id * * @param string $itemId - * @return \Magento\Backend\Model\Menu\Item|null + * @return Item|null */ public function get($itemId) { $result = null; + /** @var Item $item */ foreach ($this as $item) { - /** @var $item \Magento\Backend\Model\Menu\Item */ if ($item->getId() == $itemId) { $result = $item; break; @@ -116,8 +143,8 @@ public function move($itemId, $toItemId, $sortIndex = null) public function remove($itemId) { $result = false; + /** @var Item $item */ foreach ($this as $key => $item) { - /** @var $item \Magento\Backend\Model\Menu\Item */ if ($item->getId() == $itemId) { unset($this[$key]); $result = true; @@ -144,8 +171,8 @@ public function remove($itemId) public function reorder($itemId, $position) { $result = false; + /** @var Item $item */ foreach ($this as $key => $item) { - /** @var $item \Magento\Backend\Model\Menu\Item */ if ($item->getId() == $itemId) { unset($this[$key]); $this->add($item, null, $position); @@ -161,10 +188,10 @@ public function reorder($itemId, $position) /** * Check whether provided item is last in list * - * @param \Magento\Backend\Model\Menu\Item $item + * @param Item $item * @return bool */ - public function isLast(\Magento\Backend\Model\Menu\Item $item) + public function isLast(Item $item) { return $this->offsetGet(max(array_keys($this->getArrayCopy())))->getId() == $item->getId(); } @@ -172,12 +199,12 @@ public function isLast(\Magento\Backend\Model\Menu\Item $item) /** * Find first menu item that user is able to access * - * @return \Magento\Backend\Model\Menu\Item|null + * @return Item|null */ public function getFirstAvailable() { $result = null; - /** @var $item \Magento\Backend\Model\Menu\Item */ + /** @var Item $item */ foreach ($this as $item) { if ($item->isAllowed() && !$item->isDisabled()) { if ($item->hasChildren()) { @@ -198,7 +225,7 @@ public function getFirstAvailable() * Get parent items by item id * * @param string $itemId - * @return \Magento\Backend\Model\Menu\Item[] + * @return Item[] */ public function getParentItems($itemId) { @@ -217,8 +244,8 @@ public function getParentItems($itemId) */ protected function _findParentItems($menu, $itemId, &$parents) { + /** @var Item $item */ foreach ($menu as $item) { - /** @var $item \Magento\Backend\Model\Menu\Item */ if ($item->getId() == $itemId) { return true; } @@ -233,16 +260,54 @@ protected function _findParentItems($menu, $itemId, &$parents) } /** - * Hack to unset logger instance which cannot be serialized + * Serialize menu * * @return string */ public function serialize() { - $logger = $this->_logger; - unset($this->_logger); - $result = parent::serialize(); - $this->_logger = $logger; - return $result; + return $this->serializer->serialize($this->toArray()); + } + + /** + * Get menu data represented as an array + * + * @return array + */ + public function toArray() + { + $data = []; + foreach ($this as $item) { + $data[] = $item->toArray(); + } + return $data; + } + + /** + * Unserialize menu + * + * @param string $serialized + * @return void + */ + public function unserialize($serialized) + { + $data = $this->serializer->unserialize($serialized); + $this->populateFromArray($data); + } + + /** + * Populate the menu with data from array + * + * @param array $data + * @return void + */ + public function populateFromArray(array $data) + { + $items = []; + foreach ($data as $itemData) { + $item = $this->menuItemFactory->create($itemData); + $items[] = $item; + } + $this->exchangeArray($items); } } diff --git a/app/code/Magento/Backend/Model/Menu/AbstractDirector.php b/app/code/Magento/Backend/Model/Menu/AbstractDirector.php index 713b45fabd927..9d08ff6a186d8 100644 --- a/app/code/Magento/Backend/Model/Menu/AbstractDirector.php +++ b/app/code/Magento/Backend/Model/Menu/AbstractDirector.php @@ -1,6 +1,6 @@ query('/config/menu/*'); diff --git a/app/code/Magento/Backend/Model/Menu/Config/Menu/Dom.php b/app/code/Magento/Backend/Model/Menu/Config/Menu/Dom.php index 37da3a0aeaf3d..5d2c6dc2cbf36 100644 --- a/app/code/Magento/Backend/Model/Menu/Config/Menu/Dom.php +++ b/app/code/Magento/Backend/Model/Menu/Config/Menu/Dom.php @@ -1,6 +1,6 @@ _validator = $validator; $this->_validator->validate($data); - $this->_moduleManager = $moduleManager; $this->_acl = $authorization; $this->_scopeConfig = $scopeConfig; $this->_menuFactory = $menuFactory; $this->_urlModel = $urlModel; - $this->_moduleName = isset($data['module']) ? $data['module'] : 'Magento_Backend'; $this->_moduleList = $moduleList; - - $this->_id = $data['id']; - $this->_title = $data['title']; - $this->_action = $this->_getArgument($data, 'action'); - $this->_resource = $this->_getArgument($data, 'resource'); - $this->_dependsOnModule = $this->_getArgument($data, 'dependsOnModule'); - $this->_dependsOnConfig = $this->_getArgument($data, 'dependsOnConfig'); - $this->_tooltip = $this->_getArgument($data, 'toolTip', ''); + $this->populateFromArray($data); } /** @@ -208,6 +208,16 @@ public function getId() return $this->_id; } + /** + * Retrieve item target + * + * @return string|null + */ + public function getTarget() + { + return $this->target; + } + /** * Check whether item has subnodes * @@ -215,13 +225,13 @@ public function getId() */ public function hasChildren() { - return !is_null($this->_submenu) && (bool)$this->_submenu->count(); + return (null !== $this->_submenu) && (bool)$this->_submenu->count(); } /** * Retrieve submenu * - * @return \Magento\Backend\Model\Menu + * @return Menu */ public function getChildren() { @@ -425,7 +435,7 @@ protected function _isModuleDependenciesAvailable() protected function _isConfigDependenciesAvailable() { if ($this->_dependsOnConfig) { - return $this->_scopeConfig->isSetFlag((string)$this->_dependsOnConfig, \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + return $this->_scopeConfig->isSetFlag((string)$this->_dependsOnConfig, ScopeInterface::SCOPE_STORE); } return true; } @@ -445,45 +455,55 @@ public function isAllowed() } /** - * @return string[] + * Get menu item data represented as an array + * + * @return array */ - public function __sleep() + public function toArray() { - if ($this->_submenu) { - $this->_serializedSubmenu = $this->_submenu->serialize(); - } return [ - '_parentId', - '_moduleName', - '_sortIndex', - '_dependsOnConfig', - '_id', - '_resource', - '_path', - '_action', - '_dependsOnModule', - '_tooltip', - '_title', - '_serializedSubmenu' + 'parent_id' => $this->_parentId, + 'module_name' => $this->_moduleName, + 'sort_index' => $this->_sortIndex, + 'depends_on_config' => $this->_dependsOnConfig, + 'id' => $this->_id, + 'resource' => $this->_resource, + 'path' => $this->_path, + 'action' => $this->_action, + 'depends_on_module' => $this->_dependsOnModule, + 'tooltip' => $this->_tooltip, + 'title' => $this->_title, + 'target' => $this->target, + 'sub_menu' => isset($this->_submenu) ? $this->_submenu->toArray() : null ]; } /** + * Populate the menu item with data from array + * + * @param array $data * @return void */ - public function __wakeup() + public function populateFromArray(array $data) { - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - $this->_moduleManager = $objectManager->get(\Magento\Framework\Module\Manager::class); - $this->_validator = $objectManager->get(\Magento\Backend\Model\Menu\Item\Validator::class); - $this->_acl = $objectManager->get(\Magento\Framework\AuthorizationInterface::class); - $this->_scopeConfig = $objectManager->get(\Magento\Framework\App\Config\ScopeConfigInterface::class); - $this->_menuFactory = $objectManager->get(\Magento\Backend\Model\MenuFactory::class); - $this->_urlModel = $objectManager->get(\Magento\Backend\Model\UrlInterface::class); - $this->_moduleList = $objectManager->get(\Magento\Framework\Module\ModuleListInterface::class); - if ($this->_serializedSubmenu) { - $this->_submenu = $this->_menuFactory->create(); - $this->_submenu->unserialize($this->_serializedSubmenu); + $this->_parentId = $this->_getArgument($data, 'parent_id'); + $this->_moduleName = $this->_getArgument($data, 'module_name', 'Magento_Backend'); + $this->_sortIndex = $this->_getArgument($data, 'sort_index'); + $this->_dependsOnConfig = $this->_getArgument($data, 'depends_on_config'); + $this->_id = $this->_getArgument($data, 'id'); + $this->_resource = $this->_getArgument($data, 'resource'); + $this->_path = $this->_getArgument($data, 'path', ''); + $this->_action = $this->_getArgument($data, 'action'); + $this->_dependsOnModule = $this->_getArgument($data, 'depends_on_module'); + $this->_tooltip = $this->_getArgument($data, 'tooltip', ''); + $this->_title = $this->_getArgument($data, 'title'); + $this->target = $this->_getArgument($data, 'target'); + if (isset($data['sub_menu'])) { + $menu = $this->_menuFactory->create(); + $menu->populateFromArray($data['sub_menu']); + $this->_submenu = $menu; + } else { + $this->_submenu = null; } } } diff --git a/app/code/Magento/Backend/Model/Menu/Item/Factory.php b/app/code/Magento/Backend/Model/Menu/Item/Factory.php index aabb6fb704d3f..8d2a7fdf37144 100644 --- a/app/code/Magento/Backend/Model/Menu/Item/Factory.php +++ b/app/code/Magento/Backend/Model/Menu/Item/Factory.php @@ -1,6 +1,6 @@ _encryptor = $encryptor; + $hostChecker = $hostChecker ?: ObjectManager::getInstance()->get(HostChecker::class); parent::__construct( $routeConfig, $request, @@ -133,7 +139,8 @@ public function __construct( $scopeConfig, $routeParamsPreprocessor, $scopeType, - $data + $data, + $hostChecker ); $this->_backendHelper = $backendHelper; $this->_menuConfig = $menuConfig; diff --git a/app/code/Magento/Backend/Model/Url/ScopeResolver.php b/app/code/Magento/Backend/Model/Url/ScopeResolver.php index 596465415d77f..923341d4a30c8 100644 --- a/app/code/Magento/Backend/Model/Url/ScopeResolver.php +++ b/app/code/Magento/Backend/Model/Url/ScopeResolver.php @@ -1,6 +1,6 @@ + * + * + * Condition\Implementation + * + * + * + * + * Registered condition can be used by ui component declaration in layout + * + * + * + * "condition" just another optional attribute of ui component declaration + */ +interface ConditionInterface +{ + /** + * Validate logical condition for ui component + * If validation passed block will be displayed + * + * @return bool + */ + public function validate(); +} diff --git a/app/code/Magento/Backend/Model/View/Layout/ConditionPool.php b/app/code/Magento/Backend/Model/View/Layout/ConditionPool.php new file mode 100644 index 0000000000000..ff73b8c519824 --- /dev/null +++ b/app/code/Magento/Backend/Model/View/Layout/ConditionPool.php @@ -0,0 +1,60 @@ +conditions = $conditions; + $this->objectManager = $objectManager; + } + + /** + * Returns condition by name + * If unknown condition requested throws InputException + * + * @param string $name + * @return ConditionInterface + * @throws InputException + */ + public function getCondition($name) + { + if (!isset($this->conditions[$name])) { + throw new InputException(__('Cannot apply unknown condition')); + } + return $this->objectManager->get($this->conditions[$name]); + } +} diff --git a/app/code/Magento/Backend/Model/View/Layout/Filter.php b/app/code/Magento/Backend/Model/View/Layout/Filter.php new file mode 100644 index 0000000000000..ed0752d2222fc --- /dev/null +++ b/app/code/Magento/Backend/Model/View/Layout/Filter.php @@ -0,0 +1,52 @@ +filters = $filters; + } + + /** + * Filter structure elements + * + * @param ScheduledStructure $scheduledStructure + * @param Structure $structure + * @return bool + */ + public function filterElement(ScheduledStructure $scheduledStructure, Structure $structure) + { + foreach ($this->filters as $filter) { + $filter->filterElement($scheduledStructure, $structure); + } + return true; + } +} diff --git a/app/code/Magento/Backend/Model/View/Layout/Filter/Acl.php b/app/code/Magento/Backend/Model/View/Layout/Filter/Acl.php index 7303bccc93893..ac2e86efeb62e 100644 --- a/app/code/Magento/Backend/Model/View/Layout/Filter/Acl.php +++ b/app/code/Magento/Backend/Model/View/Layout/Filter/Acl.php @@ -2,15 +2,30 @@ /** * ACL block filter * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\Model\View\Layout\Filter; use Magento\Framework\View\Layout\ScheduledStructure; use Magento\Framework\View\Layout\Data\Structure; +use Magento\Backend\Model\View\Layout\FilterInterface; +use Magento\Backend\Model\View\Layout\StructureManager; +use Magento\Framework\App\ObjectManager; -class Acl +/** + * Class Acl + * + * Manage visibility of ui components and blocks at the backend according to ACL resources. + * If user role has corresponding resource block will be displayed. + * + * @see usage details in \Magento\Backend\Model\View\Layout\FilterInterface description + * + * Example of declaration in layout + * + * + */ +class Acl implements FilterInterface { /** * Authorization @@ -19,6 +34,11 @@ class Acl */ protected $authorization; + /** + * @var StructureManager + */ + private $structureManager; + /** * @param \Magento\Framework\AuthorizationInterface $authorization */ @@ -27,12 +47,25 @@ public function __construct(\Magento\Framework\AuthorizationInterface $authoriza $this->authorization = $authorization; } + /** + * @return StructureManager + */ + private function getStructureManager() + { + if (!$this->structureManager) { + $this->structureManager = ObjectManager::getInstance()->get(StructureManager::class); + } + return $this->structureManager; + } + /** * Delete elements that have "acl" attribute but value is "not allowed" * In any case, the "acl" attribute will be unset * * @param ScheduledStructure $scheduledStructure * @param Structure $structure + * + * @return void */ public function filterAclElements(ScheduledStructure $scheduledStructure, Structure $structure) { @@ -61,14 +94,18 @@ protected function removeElement( $elementName, $isChild = false ) { - $elementsToRemove = array_keys($structure->getChildren($elementName)); - $scheduledStructure->unsetElement($elementName); - foreach ($elementsToRemove as $element) { - $this->removeElement($scheduledStructure, $structure, $element, true); - } - if (!$isChild) { - $structure->unsetElement($elementName); - } + $this->getStructureManager()->removeElement($scheduledStructure, $structure, $elementName, $isChild); return $this; } + + /** + * @param ScheduledStructure $scheduledStructure + * @param Structure $structure + * @return bool + */ + public function filterElement(ScheduledStructure $scheduledStructure, Structure $structure) + { + $this->filterAclElements($scheduledStructure, $structure); + return true; + } } diff --git a/app/code/Magento/Backend/Model/View/Layout/Filter/Condition.php b/app/code/Magento/Backend/Model/View/Layout/Filter/Condition.php new file mode 100644 index 0000000000000..cbb72ccb51cc1 --- /dev/null +++ b/app/code/Magento/Backend/Model/View/Layout/Filter/Condition.php @@ -0,0 +1,67 @@ +structureManager = $structureManager; + $this->conditionPool = $conditionPool; + } + + /** + * Filter structure according to declared conditions + * + * @param ScheduledStructure $scheduledStructure + * @param Structure $structure + * @return bool + */ + public function filterElement(ScheduledStructure $scheduledStructure, Structure $structure) + { + foreach ($scheduledStructure->getElements() as $name => $data) { + list(, $data) = $data; + if (isset($data['attributes']['condition']) && $data['attributes']['condition']) { + $condition = $this->conditionPool->getCondition($data['attributes']['condition']); + if (!$condition->validate()) { + $this->structureManager->removeElement($scheduledStructure, $structure, $name); + } + } + } + return true; + } +} diff --git a/app/code/Magento/Backend/Model/View/Layout/FilterInterface.php b/app/code/Magento/Backend/Model/View/Layout/FilterInterface.php new file mode 100644 index 0000000000000..a1b0a302c0f66 --- /dev/null +++ b/app/code/Magento/Backend/Model/View/Layout/FilterInterface.php @@ -0,0 +1,38 @@ + + * + * + * Magento\Backend\Model\View\Layout\Filter\Acl + * + * + * + * + */ +interface FilterInterface +{ + /** + * Filter structure element + * + * @param ScheduledStructure $scheduledStructure + * @param Structure $structure + * @return bool + */ + public function filterElement(ScheduledStructure $scheduledStructure, Structure $structure); +} diff --git a/app/code/Magento/Backend/Model/View/Layout/GeneratorPool.php b/app/code/Magento/Backend/Model/View/Layout/GeneratorPool.php index 25b5c3cc0b6c8..a34d5929d5db7 100644 --- a/app/code/Magento/Backend/Model/View/Layout/GeneratorPool.php +++ b/app/code/Magento/Backend/Model/View/Layout/GeneratorPool.php @@ -1,12 +1,13 @@ filter) { + $this->filter = ObjectManager::getInstance()->get(FilterInterface::class); + } + return $this->filter; + } + /** * Build structure that is based on scheduled structure * @@ -54,7 +71,7 @@ public function __construct( protected function buildStructure(ScheduledStructure $scheduledStructure, Structure $structure) { parent::buildStructure($scheduledStructure, $structure); - $this->aclFilter->filterAclElements($scheduledStructure, $structure); + $this->getFilter()->filterElement($scheduledStructure, $structure); return $this; } } diff --git a/app/code/Magento/Backend/Model/View/Layout/Reader/Block.php b/app/code/Magento/Backend/Model/View/Layout/Reader/Block.php index a9ff03434775d..150a0c7a82191 100644 --- a/app/code/Magento/Backend/Model/View/Layout/Reader/Block.php +++ b/app/code/Magento/Backend/Model/View/Layout/Reader/Block.php @@ -1,6 +1,6 @@ getChildren($elementName)); + $scheduledStructure->unsetElement($elementName); + foreach ($elementsToRemove as $element) { + $this->removeElement($scheduledStructure, $structure, $element, true); + } + if (!$isChild) { + $structure->unsetElement($elementName); + } + return true; + } +} diff --git a/app/code/Magento/Backend/Model/View/Page/Builder.php b/app/code/Magento/Backend/Model/View/Page/Builder.php index 71d8f8241ee91..8b0fe73604137 100644 --- a/app/code/Magento/Backend/Model/View/Page/Builder.php +++ b/app/code/Magento/Backend/Model/View/Page/Builder.php @@ -1,6 +1,6 @@ sectionPool = $this->getMock( - \Magento\Framework\App\Config\ScopePool::class, - ['getScope', 'clean'], + $this->appConfig = $this->getMock( + \Magento\Framework\App\Config::class, + ['get'], [], '', false ); - $this->model = new \Magento\Backend\App\Config($this->sectionPool); + $this->model = new \Magento\Backend\App\Config($this->appConfig); } public function testGetValue() { $expectedValue = 'some value'; $path = 'some path'; - $configData = $this->getConfigDataMock('getValue'); - $configData->expects( - $this->once() - )->method( - 'getValue' - )->with( - $this->equalTo($path) - )->will( - $this->returnValue($expectedValue) - ); - $this->sectionPool->expects( + $this->appConfig->expects( $this->once() )->method( - 'getScope' + 'get' )->with( - $this->equalTo('default'), + $this->equalTo('system'), + $this->equalTo('default/' . $path), $this->isNull() )->will( - $this->returnValue($configData) + $this->returnValue($expectedValue) ); $this->assertEquals($expectedValue, $this->model->getValue($path)); } - public function testSetValue() - { - $value = 'some value'; - $path = 'some path'; - $configData = $this->getConfigDataMock('setValue'); - $configData->expects($this->once())->method('setValue')->with($this->equalTo($path), $this->equalTo($value)); - $this->sectionPool->expects( - $this->once() - )->method( - 'getScope' - )->with( - $this->equalTo('default'), - $this->isNull() - )->will( - $this->returnValue($configData) - ); - $this->model->setValue($path, $value); - } - /** + * @param string $configPath * @param mixed $configValue * @param bool $expectedResult * @dataProvider isSetFlagDataProvider */ - public function testIsSetFlag($configValue, $expectedResult) + public function testIsSetFlag($configPath, $configValue, $expectedResult) { - $path = 'some path'; - $configData = $this->getConfigDataMock('getValue'); - $configData->expects( - $this->once() + $this->appConfig->expects( + $this->any() )->method( - 'getValue' + 'get' )->with( - $this->equalTo($path) + $this->equalTo('system'), + $this->equalTo('default/' . $configPath) )->will( $this->returnValue($configValue) ); - $this->sectionPool->expects( - $this->once() - )->method( - 'getScope' - )->with( - $this->equalTo('default'), - $this->isNull() - )->will( - $this->returnValue($configData) - ); - $this->assertEquals($expectedResult, $this->model->isSetFlag($path)); + $this->assertEquals($expectedResult, $this->model->isSetFlag($configPath)); } public function isSetFlagDataProvider() { return [ - [0, false], - [true, true], - ['0', false], - ['', false], - ['some string', true], - [1, true] + ['a', 0, false], + ['b', true, true], + ['c', '0', false], + ['d', '', false], + ['e', 'some string', true], + ['f', 1, true] ]; } diff --git a/app/code/Magento/Backend/Test/Unit/App/Response/Http/FileFactoryTest.php b/app/code/Magento/Backend/Test/Unit/App/Response/Http/FileFactoryTest.php index 25c32fcaeec0b..8a4555ffa105a 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Response/Http/FileFactoryTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Response/Http/FileFactoryTest.php @@ -1,6 +1,6 @@ activeMenuItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuItemCheckerMock = $this->getMockBuilder(MenuItemChecker::class) + ->disableOriginalConstructor() + ->getMock(); + $this->escaperMock = $this->getMockBuilder(Escaper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->anchorRenderer = $this->objectManagerHelper->getObject( + AnchorRenderer::class, + [ + 'menuItemChecker' => $this->menuItemCheckerMock, + 'escaper' => $this->escaperMock + ] + ); + } + + public function testRenderAnchorLevelIsOne() + { + $title = 'Title'; + $html = 'Test html'; + $this->menuItemMock->expects($this->once())->method('getUrl')->willReturn('#'); + $this->menuItemMock->expects($this->once())->method('getTitle')->willReturn($title); + $this->escaperMock->expects($this->once())->method('escapeHtml')->with(__($title))->willReturn($html); + + $expected = '' + . '' . $html . '' + . ''; + + $this->assertEquals( + $expected, + $this->anchorRenderer->renderAnchor($this->activeMenuItemMock, $this->menuItemMock, 1) + ); + } + + /** + * @param bool $hasTarget + * @dataProvider targetDataProvider + */ + public function testRenderAnchorLevelIsNotOne($hasTarget) + { + $level = 0; + $title = 'Title'; + $html = 'Test html'; + $url = 'test/url'; + $tooltip = 'Anchor title'; + $onclick = ''; + $target = '_blank'; + $finalTarget = $hasTarget ? ('target=' . $target) : ''; + $this->menuItemMock->expects($this->any())->method('getTarget')->willReturn($hasTarget ? $target : null); + $this->menuItemMock->expects($this->once())->method('getUrl')->willReturn($url); + $this->menuItemMock->expects($this->once())->method('getTitle')->willReturn($title); + $this->escaperMock->expects($this->once())->method('escapeHtml')->with(__($title))->willReturn($html); + $this->menuItemMock->expects($this->once())->method('hasTooltip')->willReturn(true); + $this->menuItemMock->expects($this->any())->method('getTooltip')->willReturn(__($tooltip)); + $this->menuItemMock->expects($this->once())->method('hasClickCallback')->willReturn(true); + $this->menuItemMock->expects($this->once())->method('getClickCallback')->willReturn($onclick); + $this->menuItemCheckerMock->expects($this->once()) + ->method('isItemActive') + ->with($this->activeMenuItemMock, $this->menuItemMock, $level)->willReturn(true); + + $expected = '' . '' . $html + . '' . ''; + + $this->assertEquals( + $expected, + $this->anchorRenderer->renderAnchor($this->activeMenuItemMock, $this->menuItemMock, $level) + ); + } + + public function targetDataProvider() + { + return [ + 'item has target' => [true], + 'item does not have target' => [false] + ]; + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php b/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php index 7b78821bff498..395620cff9288 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php @@ -1,6 +1,6 @@ menuItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + $this->activeMenuItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuItemChecker = new MenuItemChecker(); + } + + /** + * @param int $activeItemId + * @param int $itemId + * @param bool $isItem + * @param bool $expected + * @dataProvider dataProvider + */ + public function testIsItemActive($activeItemId, $itemId, $isItem, $expected) + { + $this->menuMock = $this->getMockBuilder(Menu::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuItemMock->expects($this->any())->method('getId')->willReturn($itemId); + $this->activeMenuItemMock->expects($this->any())->method('getId')->willReturn($activeItemId); + $this->menuItemMock->expects($this->any())->method('getChildren')->willReturn($this->menuMock); + $this->menuMock->expects($this->any()) + ->method('get') + ->with($activeItemId) + ->willReturn($isItem ? $this->activeMenuItemMock : null); + $this->assertEquals( + $expected, + $this->menuItemChecker->isItemActive($this->activeMenuItemMock, $this->menuItemMock, 0) + ); + } + + public function testIsItemActiveLevelNotZero() + { + $this->assertFalse( + $this->menuItemChecker->isItemActive($this->activeMenuItemMock, $this->menuItemMock, 1) + ); + } + + public function dataProvider() + { + return [ + 'outputItemEquals' => ['1', '1', false, true], + 'outputItemIsChild' => ['1', '2', true, true], + 'outputItemIsChildNull' => ['1', '2', false, false], + ]; + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Block/MenuTest.php b/app/code/Magento/Backend/Test/Unit/Block/MenuTest.php new file mode 100644 index 0000000000000..ccc0e719aaecb --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Block/MenuTest.php @@ -0,0 +1,146 @@ +activeItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlMock = $this->getMockBuilder(UrlInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->iteratorFactoryMock = $this->getMockBuilder(IteratorFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->authSessionMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuConfigMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->localeResolverMock = $this->getMockBuilder(ResolverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menuItemChecker = $this->getMockBuilder(MenuItemChecker::class) + ->disableOriginalConstructor() + ->getMock(); + $this->anchorRendererMock = $this->getMockBuilder(AnchorRenderer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->menu = $this->objectManagerHelper->getObject( + Menu::class, + [ + 'url' => $this->urlMock, + 'iteratorFactory' => $this->iteratorFactoryMock, + 'authSession' => $this->authSessionMock, + 'menuConfig' => $this->menuConfigMock, + 'localeResolver' => $this->localeResolverMock, + 'menuItemChecker' => $this->menuItemCheckerMock, + 'anchorRenderer' => $this->anchorRendererMock + ] + ); + } + + public function testGetActiveItemModelMenuIsNotNull() + { + $this->menuModelMock = $this->getMockBuilder(MenuModel::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menu->setActive($this->activeItemMock); + $this->menuConfigMock->expects($this->once())->method('getMenu')->willReturn($this->menuModelMock); + $this->menuModelMock->expects($this->once()) + ->method('get') + ->willReturn($this->activeItemMock); + + $this->assertEquals($this->activeItemMock, $this->menu->getActiveItemModel()); + } + + public function testGetActiveItemModelMenuIsNull() + { + $this->menuModelMock = $this->getMockBuilder(MenuModel::class) + ->disableOriginalConstructor() + ->getMock(); + $this->menu->setActive(null); + $this->menuConfigMock->expects($this->once())->method('getMenu')->willReturn($this->menuModelMock); + $this->menuModelMock->expects($this->once()) + ->method('get') + ->willReturn(null); + + $this->assertFalse($this->menu->getActiveItemModel()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Block/Page/System/Config/Robots/ResetTest.php b/app/code/Magento/Backend/Test/Unit/Block/Page/System/Config/Robots/ResetTest.php index 93ee165a541df..2eba4779d91e6 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Page/System/Config/Robots/ResetTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Page/System/Config/Robots/ResetTest.php @@ -1,6 +1,6 @@ _gridMock = $this->getMock( - \Magento\Backend\Block\Widget\Grid::class, - ['getId', 'getCollection'], - [], - '', - false - ); - $this->_gridMock->expects($this->any())->method('getId')->will($this->returnValue('test_grid')); - - $this->_layoutMock = $this->getMock( - \Magento\Framework\View\Layout::class, - ['getParentName', 'getBlock', 'helper'], - [], - '', - false, - false - ); + $this->_gridMock = $this->getMockBuilder(\Magento\Backend\Block\Widget\Grid::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->setMethods(['getId', 'getCollection']) + ->getMock(); + $this->_gridMock->expects($this->any()) + ->method('getId') + ->willReturn('test_grid'); - $this->_layoutMock->expects( - $this->any() - )->method( - 'getParentName' - )->with( - 'test_grid_massaction' - )->will( - $this->returnValue('test_grid') - ); - $this->_layoutMock->expects( - $this->any() - )->method( - 'getBlock' - )->with( - 'test_grid' - )->will( - $this->returnValue($this->_gridMock) - ); + $this->_layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->setMethods(['getParentName', 'getBlock', 'helper']) + ->getMock(); + $this->_layoutMock->expects($this->any()) + ->method('getParentName') + ->with('test_grid_massaction') + ->willReturn('test_grid'); + $this->_layoutMock->expects($this->any()) + ->method('getBlock') + ->with('test_grid') + ->willReturn($this->_gridMock); + + $this->_requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); - $this->_requestMock = $this->getMock(\Magento\Framework\App\Request\Http::class, [], [], '', false); + $this->_urlModelMock = $this->getMockBuilder(\Magento\Backend\Model\Url::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); - $this->_urlModelMock = $this->getMock(\Magento\Backend\Model\Url::class, [], [], '', false); + $this->visibilityCheckerMock = $this->getMockBuilder(VisibilityChecker::class) + ->getMockForAbstractClass(); $arguments = [ 'layout' => $this->_layoutMock, 'request' => $this->_requestMock, 'urlBuilder' => $this->_urlModelMock, - 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'], + 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'] ]; $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -124,26 +126,24 @@ public function testMassactionDefaultValues() } /** - * @param $itemId - * @param $item + * @param string $itemId + * @param \Magento\Framework\DataObject $item * @param $expectedItem \Magento\Framework\DataObject - * @dataProvider itemsDataProvider + * @dataProvider itemsProcessingDataProvider */ public function testItemsProcessing($itemId, $item, $expectedItem) { - $this->_urlModelMock->expects( - $this->any() - )->method( - 'getBaseUrl' - )->will( - $this->returnValue('http://localhost/index.php') - ); + $this->_urlModelMock->expects($this->any()) + ->method('getBaseUrl') + ->willReturn('http://localhost/index.php'); $urlReturnValueMap = [ ['*/*/test1', [], 'http://localhost/index.php/backend/admin/test/test1'], ['*/*/test2', [], 'http://localhost/index.php/backend/admin/test/test2'], ]; - $this->_urlModelMock->expects($this->any())->method('getUrl')->will($this->returnValueMap($urlReturnValueMap)); + $this->_urlModelMock->expects($this->any()) + ->method('getUrl') + ->willReturnMap($urlReturnValueMap); $this->_block->addItem($itemId, $item); $this->assertEquals(1, $this->_block->getCount()); @@ -157,7 +157,10 @@ public function testItemsProcessing($itemId, $item, $expectedItem) $this->assertNull($this->_block->getItem($itemId)); } - public function itemsDataProvider() + /** + * @return array + */ + public function itemsProcessingDataProvider() { return [ [ @@ -186,22 +189,17 @@ public function itemsDataProvider() } /** - * @param $param - * @param $expectedJson - * @param $expected + * @param string $param + * @param string $expectedJson + * @param array $expected * @dataProvider selectedDataProvider */ public function testSelected($param, $expectedJson, $expected) { - $this->_requestMock->expects( - $this->any() - )->method( - 'getParam' - )->with( - $this->_block->getFormFieldNameInternal() - )->will( - $this->returnValue($param) - ); + $this->_requestMock->expects($this->any()) + ->method('getParam') + ->with($this->_block->getFormFieldNameInternal()) + ->willReturn($param); $this->assertEquals($expectedJson, $this->_block->getSelectedJson()); $this->assertEquals($expected, $this->_block->getSelected()); @@ -262,6 +260,9 @@ public function testGetGridIdsJsonWithUseSelectAll(array $items, $result) $this->assertEquals($result, $this->_block->getGridIdsJson()); } + /** + * @return array + */ public function dataProviderGetGridIdsJsonWithUseSelectAll() { return [ @@ -279,4 +280,71 @@ public function dataProviderGetGridIdsJsonWithUseSelectAll() ], ]; } + + /** + * @param string $itemId + * @param array|\Magento\Framework\DataObject $item + * @param int $count + * @param bool $withVisibilityChecker + * @param bool $isVisible + * @dataProvider addItemDataProvider + */ + public function testAddItem($itemId, $item, $count, $withVisibilityChecker, $isVisible) + { + $this->visibilityCheckerMock->expects($this->any()) + ->method('isVisible') + ->willReturn($isVisible); + + if ($withVisibilityChecker) { + $item['visible'] = $this->visibilityCheckerMock; + } + + $urlReturnValueMap = [ + ['*/*/test1', [], 'http://localhost/index.php/backend/admin/test/test1'], + ['*/*/test2', [], 'http://localhost/index.php/backend/admin/test/test2'], + ]; + $this->_urlModelMock->expects($this->any()) + ->method('getUrl') + ->willReturnMap($urlReturnValueMap); + + $this->_block->addItem($itemId, $item); + $this->assertEquals($count, $this->_block->getCount()); + } + + /** + * @return array + */ + public function addItemDataProvider() + { + return [ + [ + 'itemId' => 'test1', + 'item' => ['label' => 'Test 1', 'url' => '*/*/test1'], + 'count' => 1, + 'withVisibilityChecker' => false, + '$isVisible' => false, + ], + [ + 'itemId' => 'test2', + 'item' => ['label' => 'Test 2', 'url' => '*/*/test2'], + 'count' => 1, + 'withVisibilityChecker' => false, + 'isVisible' => true, + ], + [ + 'itemId' => 'test1', + 'item' => ['label' => 'Test 1. Hide', 'url' => '*/*/test1'], + 'count' => 0, + 'withVisibilityChecker' => true, + 'isVisible' => false, + ], + [ + 'itemId' => 'test2', + 'item' => ['label' => 'Test 2. Does not hide', 'url' => '*/*/test2'], + 'count' => 1, + 'withVisibilityChecker' => true, + 'isVisible' => true, + ] + ]; + } } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/SerializerTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/SerializerTest.php index 1d7caa432e59e..627b6dbec4350 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/SerializerTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/SerializerTest.php @@ -1,6 +1,6 @@ stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(MessageManager::class) + ->getMockForAbstractClass(); + + $this->requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + + $this->cacheTypeListMock = $this->getMockBuilder(CacheTypeList::class) + ->getMockForAbstractClass(); + + $this->cacheStateMock = $this->getMockBuilder(CacheState::class) + ->getMockForAbstractClass(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $this->redirectMock->expects($this->once()) + ->method('setPath') + ->with('adminhtml/*') + ->willReturnSelf(); + $resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $contextMock->expects($this->once()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + $contextMock->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactoryMock); + $contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->controller = $objectManagerHelper->getObject( + MassDisable::class, + [ + 'context' => $contextMock, + 'cacheTypeList' => $this->cacheTypeListMock, + 'cacheState' => $this->cacheStateMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty($this->controller, 'state', $this->stateMock); + } + + public function testExecuteInProductionMode() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_PRODUCTION); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with('You can\'t change status of cache type(s) in production mode', null) + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteInvalidTypeCache() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn(['someCache']); + + $this->messageManagerMock->expects($this->once()) + ->method('addError') + ->with('Specified cache type(s) don\'t exist: someCache') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteWithException() + { + $exception = new \Exception(); + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->willThrowException($exception); + + $this->messageManagerMock->expects($this->once()) + ->method('addException') + ->with($exception, 'An error occurred while disabling cache.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteSuccess() + { + $cacheType = 'pageCache'; + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn([$cacheType]); + + $this->cacheStateMock->expects($this->once()) + ->method('isEnabled') + ->with($cacheType) + ->willReturn(true); + $this->cacheStateMock->expects($this->once()) + ->method('setEnabled') + ->with($cacheType, false); + $this->cacheStateMock->expects($this->once()) + ->method('persist'); + + $this->messageManagerMock->expects($this->once()) + ->method('addSuccess') + ->with('1 cache type(s) disabled.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php new file mode 100644 index 0000000000000..aff391e033209 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php @@ -0,0 +1,224 @@ +stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(MessageManager::class) + ->getMockForAbstractClass(); + + $this->requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + + $this->cacheTypeListMock = $this->getMockBuilder(CacheTypeList::class) + ->getMockForAbstractClass(); + + $this->cacheStateMock = $this->getMockBuilder(CacheState::class) + ->getMockForAbstractClass(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $this->redirectMock->expects($this->once()) + ->method('setPath') + ->with('adminhtml/*') + ->willReturnSelf(); + $resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $contextMock->expects($this->once()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + $contextMock->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactoryMock); + $contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->controller = $objectManagerHelper->getObject( + MassEnable::class, + [ + 'context' => $contextMock, + 'cacheTypeList' => $this->cacheTypeListMock, + 'cacheState' => $this->cacheStateMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty($this->controller, 'state', $this->stateMock); + } + + public function testExecuteInProductionMode() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_PRODUCTION); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with('You can\'t change status of cache type(s) in production mode', null) + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteInvalidTypeCache() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn(['someCache']); + + $this->messageManagerMock->expects($this->once()) + ->method('addError') + ->with('Specified cache type(s) don\'t exist: someCache') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteWithException() + { + $exception = new \Exception(); + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->willThrowException($exception); + + $this->messageManagerMock->expects($this->once()) + ->method('addException') + ->with($exception, 'An error occurred while enabling cache.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteSuccess() + { + $cacheType = 'pageCache'; + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn([$cacheType]); + + $this->cacheStateMock->expects($this->once()) + ->method('isEnabled') + ->with($cacheType) + ->willReturn(false); + $this->cacheStateMock->expects($this->once()) + ->method('setEnabled') + ->with($cacheType, true); + $this->cacheStateMock->expects($this->once()) + ->method('persist'); + + $this->messageManagerMock->expects($this->once()) + ->method('addSuccess') + ->with('1 cache type(s) enabled.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/AbstractTestCase.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/AbstractTestCase.php index 43143f0587ee7..16e109b516410 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/AbstractTestCase.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/AbstractTestCase.php @@ -1,6 +1,6 @@ resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->pageMock = $this->getMockBuilder(Page::class) + ->disableOriginalConstructor() + ->getMock(); + $this->pageConfigMock = $this->getMockBuilder(Config::class) + ->setMethods(['getTitle']) + ->disableOriginalConstructor() + ->getMock(); + $this->titleMock = $this->getMockBuilder(Title::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->indexController = $this->objectManagerHelper->getObject( + Index::class, + [ + 'resultFactory' => $this->resultFactoryMock + ] + ); + } + + public function testIndex() + { + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_PAGE) + ->willReturn($this->pageMock); + $this->pageMock->expects($this->once()) + ->method('setActiveMenu') + ->with('Magento_Backend::system_store') + ->willReturnSelf(); + $this->pageMock->expects($this->exactly(2)) + ->method('addBreadcrumb') + ->withConsecutive( + [__('Stores'), __('Stores')], + [__('All Stores'), __('All Stores')] + ); + $this->pageMock->expects($this->once()) + ->method('getConfig') + ->willReturn($this->pageConfigMock); + $this->pageConfigMock->expects($this->once())->method('getTitle')->willReturn($this->titleMock); + $this->titleMock->expects($this->once())->method('prepend')->with(__('Stores'))->willReturn($this->pageMock); + + $this->assertSame($this->pageMock, $this->indexController->execute()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Cron/CleanCacheTest.php b/app/code/Magento/Backend/Test/Unit/Cron/CleanCacheTest.php index 4386e7c395300..7e8fbfe197bf9 100644 --- a/app/code/Magento/Backend/Test/Unit/Cron/CleanCacheTest.php +++ b/app/code/Magento/Backend/Test/Unit/Cron/CleanCacheTest.php @@ -1,6 +1,6 @@ _factoryMock = $this->getMock(\Magento\Backend\Model\Menu\Item\Factory::class, [], [], '', false); - $this->_menuMock = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] + $this->factoryMock = $this->getMock(\Magento\Backend\Model\Menu\Item\Factory::class, [], [], '', false); + $this->menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); + + $this->model = (new ObjectManager($this))->getObject( + \Magento\Backend\Model\Menu\Builder::class, + [ + 'menuItemFactory' => $this->factoryMock + ] ); - - $this->_model = new \Magento\Backend\Model\Menu\Builder($this->_factoryMock, $this->_menuMock); } public function testProcessCommand() @@ -41,20 +44,20 @@ public function testProcessCommand() $command2 = $this->getMock(\Magento\Backend\Model\Menu\Builder\Command\Update::class, [], [], '', false); $command2->expects($this->any())->method('getId')->will($this->returnValue(1)); $command->expects($this->once())->method('chain')->with($this->equalTo($command2)); - $this->_model->processCommand($command); - $this->_model->processCommand($command2); + $this->model->processCommand($command); + $this->model->processCommand($command2); } public function testGetResultBuildsTreeStructure() { $item1 = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $item1->expects($this->once())->method('getChildren')->will($this->returnValue($this->_menuMock)); - $this->_factoryMock->expects($this->any())->method('create')->will($this->returnValue($item1)); + $item1->expects($this->once())->method('getChildren')->will($this->returnValue($this->menuMock)); + $this->factoryMock->expects($this->any())->method('create')->will($this->returnValue($item1)); $item2 = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $this->_factoryMock->expects($this->at(1))->method('create')->will($this->returnValue($item2)); + $this->factoryMock->expects($this->at(1))->method('create')->will($this->returnValue($item2)); - $this->_menuMock->expects( + $this->menuMock->expects( $this->at(0) )->method( 'add' @@ -64,7 +67,7 @@ public function testGetResultBuildsTreeStructure() $this->equalTo(2) ); - $this->_menuMock->expects( + $this->menuMock->expects( $this->at(1) )->method( 'add' @@ -74,7 +77,7 @@ public function testGetResultBuildsTreeStructure() $this->equalTo(4) ); - $this->_model->processCommand( + $this->model->processCommand( new \Magento\Backend\Model\Menu\Builder\Command\Add( [ 'id' => 'item1', @@ -85,7 +88,7 @@ public function testGetResultBuildsTreeStructure() ] ) ); - $this->_model->processCommand( + $this->model->processCommand( new \Magento\Backend\Model\Menu\Builder\Command\Add( [ 'id' => 'item2', @@ -98,12 +101,12 @@ public function testGetResultBuildsTreeStructure() ) ); - $this->_model->getResult($this->_menuMock); + $this->model->getResult($this->menuMock); } public function testGetResultSkipsRemovedItems() { - $this->_model->processCommand( + $this->model->processCommand( new \Magento\Backend\Model\Menu\Builder\Command\Add( [ 'id' => 1, @@ -113,11 +116,11 @@ public function testGetResultSkipsRemovedItems() ] ) ); - $this->_model->processCommand(new \Magento\Backend\Model\Menu\Builder\Command\Remove(['id' => 1])); + $this->model->processCommand(new \Magento\Backend\Model\Menu\Builder\Command\Remove(['id' => 1])); - $this->_menuMock->expects($this->never())->method('addChild'); + $this->menuMock->expects($this->never())->method('addChild'); - $this->_model->getResult($this->_menuMock); + $this->model->getResult($this->menuMock); } /** @@ -126,9 +129,9 @@ public function testGetResultSkipsRemovedItems() public function testGetResultSkipItemsWithInvalidParent() { $item1 = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $this->_factoryMock->expects($this->any())->method('create')->will($this->returnValue($item1)); + $this->factoryMock->expects($this->any())->method('create')->will($this->returnValue($item1)); - $this->_model->processCommand( + $this->model->processCommand( new \Magento\Backend\Model\Menu\Builder\Command\Add( [ 'id' => 'item1', @@ -140,6 +143,6 @@ public function testGetResultSkipItemsWithInvalidParent() ) ); - $this->_model->getResult($this->_menuMock); + $this->model->getResult($this->menuMock); } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/ConverterTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/ConverterTest.php index a05526c715815..e60f32218138b 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/ConverterTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/ConverterTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/valid_menu.xml b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/valid_menu.xml index 4627fb49a031f..d60f5a0cd37da 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/valid_menu.xml +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/valid_menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php index dee7518be3ac9..424cf696601ba 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php @@ -1,63 +1,47 @@ _cacheInstanceMock = $this->getMock( + $this->cacheInstanceMock = $this->getMock( \Magento\Framework\App\Cache\Type\Config::class, [], [], @@ -65,15 +49,7 @@ protected function setUp() false ); - $this->_directorMock = $this->getMock( - \Magento\Backend\Model\Menu\AbstractDirector::class, - [], - [], - '', - false - ); - - $this->_menuFactoryMock = $this->getMock( + $menuFactoryMock = $this->getMock( \Magento\Backend\Model\MenuFactory::class, ['create'], [], @@ -81,7 +57,7 @@ protected function setUp() false ); - $this->_configReaderMock = $this->getMock( + $this->configReaderMock = $this->getMock( \Magento\Backend\Model\Menu\Config\Reader::class, [], [], @@ -89,30 +65,15 @@ protected function setUp() false ); - $this->_eventManagerMock = $this->getMock( - \Magento\Framework\Event\ManagerInterface::class, - [], - [], - '', - false, - false - ); - - $this->_logger = $this->getMock(\Psr\Log\LoggerInterface::class); - - $this->_menuMock = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] - ); + $this->logger = $this->getMock(\Psr\Log\LoggerInterface::class); - $this->_menuBuilderMock = $this->getMock(\Magento\Backend\Model\Menu\Builder::class, [], [], '', false); + $this->menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); - $this->_menuFactoryMock->expects($this->any())->method('create')->will($this->returnValue($this->_menuMock)); + $this->menuBuilderMock = $this->getMock(\Magento\Backend\Model\Menu\Builder::class, [], [], '', false); - $scopeConfig = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $menuFactoryMock->expects($this->any())->method('create')->will($this->returnValue($this->menuMock)); - $this->_configReaderMock->expects($this->any())->method('read')->will($this->returnValue([])); + $this->configReaderMock->expects($this->any())->method('read')->will($this->returnValue([])); $appState = $this->getMock(\Magento\Framework\App\State::class, ['getAreaCode'], [], '', false); $appState->expects( @@ -123,22 +84,22 @@ protected function setUp() $this->returnValue(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) ); - $this->_model = new \Magento\Backend\Model\Menu\Config( - $this->_menuBuilderMock, - $this->_directorMock, - $this->_menuFactoryMock, - $this->_configReaderMock, - $this->_cacheInstanceMock, - $this->_eventManagerMock, - $this->_logger, - $scopeConfig, - $appState + $this->model = (new ObjectManager($this))->getObject( + \Magento\Backend\Model\Menu\Config::class, + [ + 'menuBuilder' => $this->menuBuilderMock, + 'menuFactory' => $menuFactoryMock, + 'configReader' => $this->configReaderMock, + 'configCacheType' => $this->cacheInstanceMock, + 'logger' => $this->logger, + 'appState' => $appState, + ] ); } public function testGetMenuWithCachedObjectReturnsUnserializedObject() { - $this->_cacheInstanceMock->expects( + $this->cacheInstanceMock->expects( $this->once() )->method( 'load' @@ -148,14 +109,14 @@ public function testGetMenuWithCachedObjectReturnsUnserializedObject() $this->returnValue('menu_cache') ); - $this->_menuMock->expects($this->once())->method('unserialize')->with('menu_cache'); + $this->menuMock->expects($this->once())->method('unserialize')->with('menu_cache'); - $this->assertEquals($this->_menuMock, $this->_model->getMenu()); + $this->assertEquals($this->menuMock, $this->model->getMenu()); } public function testGetMenuWithNotCachedObjectBuidlsObject() { - $this->_cacheInstanceMock->expects( + $this->cacheInstanceMock->expects( $this->at(0) )->method( 'load' @@ -165,17 +126,17 @@ public function testGetMenuWithNotCachedObjectBuidlsObject() $this->returnValue(false) ); - $this->_configReaderMock->expects($this->once())->method('read')->will($this->returnValue([])); + $this->configReaderMock->expects($this->once())->method('read')->will($this->returnValue([])); - $this->_menuBuilderMock->expects( + $this->menuBuilderMock->expects( $this->exactly(1) )->method( 'getResult' )->will( - $this->returnValue($this->_menuMock) + $this->returnValue($this->menuMock) ); - $this->assertEquals($this->_menuMock, $this->_model->getMenu()); + $this->assertEquals($this->menuMock, $this->model->getMenu()); } /** @@ -186,7 +147,7 @@ public function testGetMenuWithNotCachedObjectBuidlsObject() public function testGetMenuExceptionLogged($expectedException) { $this->setExpectedException($expectedException); - $this->_menuBuilderMock->expects( + $this->menuBuilderMock->expects( $this->exactly(1) )->method( 'getResult' @@ -194,7 +155,7 @@ public function testGetMenuExceptionLogged($expectedException) $this->throwException(new $expectedException()) ); - $this->_model->getMenu(); + $this->model->getMenu(); } public function getMenuExceptionLoggedDataProvider() @@ -208,9 +169,9 @@ public function getMenuExceptionLoggedDataProvider() public function testGetMenuGenericExceptionIsNotLogged() { - $this->_logger->expects($this->never())->method('critical'); + $this->logger->expects($this->never())->method('critical'); - $this->_menuBuilderMock->expects( + $this->menuBuilderMock->expects( $this->exactly(1) )->method( 'getResult' @@ -218,7 +179,7 @@ public function testGetMenuGenericExceptionIsNotLogged() $this->throwException(new \Exception()) ); try { - $this->_model->getMenu(); + $this->model->getMenu(); } catch (\Exception $e) { return; } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Director/DirectorTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Director/DirectorTest.php index 550a0a4354832..f4b40aa20fb30 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Director/DirectorTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Director/DirectorTest.php @@ -1,6 +1,6 @@ _items['item1'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $this->_items['item1']->expects($this->any())->method('getId')->will($this->returnValue('item1')); - $this->_items['item1']->expects($this->any())->method('isDisabled')->will($this->returnValue(false)); - $this->_items['item1']->expects($this->any())->method('isAllowed')->will($this->returnValue(true)); - - $this->_items['item2'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $this->_items['item2']->expects($this->any())->method('getId')->will($this->returnValue('item2')); - $this->_items['item2']->expects($this->any())->method('isDisabled')->will($this->returnValue(true)); - $this->_items['item2']->expects($this->any())->method('isAllowed')->will($this->returnValue(true)); - - $this->_items['item3'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); - $this->_items['item3']->expects($this->any())->method('getId')->will($this->returnValue('item3')); - $this->_items['item3']->expects($this->any())->method('isDisabled')->will($this->returnValue(false)); - $this->_items['item3']->expects($this->any())->method('isAllowed')->will($this->returnValue(false)); - - $loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); - - $this->_menuModel = new \Magento\Backend\Model\Menu($loggerMock); + $this->items['item1'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); + $this->items['item1']->expects($this->any())->method('getId')->will($this->returnValue('item1')); + $this->items['item1']->expects($this->any())->method('isDisabled')->will($this->returnValue(false)); + $this->items['item1']->expects($this->any())->method('isAllowed')->will($this->returnValue(true)); + + $this->items['item2'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); + $this->items['item2']->expects($this->any())->method('getId')->will($this->returnValue('item2')); + $this->items['item2']->expects($this->any())->method('isDisabled')->will($this->returnValue(true)); + $this->items['item2']->expects($this->any())->method('isAllowed')->will($this->returnValue(true)); + + $this->items['item3'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); + $this->items['item3']->expects($this->any())->method('getId')->will($this->returnValue('item3')); + $this->items['item3']->expects($this->any())->method('isDisabled')->will($this->returnValue(false)); + $this->items['item3']->expects($this->any())->method('isAllowed')->will($this->returnValue(false)); + + $this->menuModel = (new ObjectManager($this))->getObject(\Magento\Backend\Model\Menu::class); } public function testLoopWithAllItemsDisabledDoesntIterate() { - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); $filterIteratorModel = new \Magento\Backend\Model\Menu\Filter\Iterator( - $this->_menuModel->getIterator() + $this->menuModel->getIterator() ); $items = []; @@ -59,15 +59,15 @@ public function testLoopWithAllItemsDisabledDoesntIterate() public function testLoopIteratesOnlyValidItems() { - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->_items['item1']); + $this->menuModel->add($this->items['item1']); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); $filterIteratorModel = new \Magento\Backend\Model\Menu\Filter\Iterator( - $this->_menuModel->getIterator() + $this->menuModel->getIterator() ); $items = []; @@ -79,16 +79,16 @@ public function testLoopIteratesOnlyValidItems() public function testLoopIteratesDosntIterateDisabledItems() { - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->_items['item1']); - $this->_menuModel->add($this->_items['item2']); + $this->menuModel->add($this->items['item1']); + $this->menuModel->add($this->items['item2']); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); $filterIteratorModel = new \Magento\Backend\Model\Menu\Filter\Iterator( - $this->_menuModel->getIterator() + $this->menuModel->getIterator() ); $items = []; @@ -100,16 +100,16 @@ public function testLoopIteratesDosntIterateDisabledItems() public function testLoopIteratesDosntIterateNotAllowedItems() { - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->_items['item1']); - $this->_menuModel->add($this->_items['item3']); + $this->menuModel->add($this->items['item1']); + $this->menuModel->add($this->items['item3']); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); $filterIteratorModel = new \Magento\Backend\Model\Menu\Filter\Iterator( - $this->_menuModel->getIterator() + $this->menuModel->getIterator() ); $items = []; @@ -121,17 +121,17 @@ public function testLoopIteratesDosntIterateNotAllowedItems() public function testLoopIteratesMixedItems() { - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->_items['item1']); - $this->_menuModel->add($this->_items['item2']); - $this->_menuModel->add($this->_items['item3']); + $this->menuModel->add($this->items['item1']); + $this->menuModel->add($this->items['item2']); + $this->menuModel->add($this->items['item3']); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); - $this->_menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); + $this->menuModel->add($this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false)); $filterIteratorModel = new \Magento\Backend\Model\Menu\Filter\Iterator( - $this->_menuModel->getIterator() + $this->menuModel->getIterator() ); $items = []; diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php index 5d7e050d32468..0391c91fa6c0e 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php @@ -1,6 +1,6 @@ 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'dependsOnModule' => 'Magento_Backend', - 'dependsOnConfig' => 'system/config/isEnabled', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', 'tooltip' => 'Item tooltip', ]; @@ -76,15 +74,15 @@ protected function setUp() ); $this->_urlModelMock = $this->getMock(\Magento\Backend\Model\Url::class, [], [], '', false); $this->_moduleManager = $this->getMock(\Magento\Framework\Module\Manager::class, [], [], '', false); - $this->_validatorMock = $this->getMock(\Magento\Backend\Model\Menu\Item\Validator::class); - $this->_validatorMock->expects($this->any())->method('validate'); + $validatorMock = $this->getMock(\Magento\Backend\Model\Menu\Item\Validator::class); + $validatorMock->expects($this->any())->method('validate'); $this->_moduleListMock = $this->getMock(\Magento\Framework\Module\ModuleListInterface::class); - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_model = $helper->getObject( + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->_model = $this->objectManager->getObject( \Magento\Backend\Model\Menu\Item::class, [ - 'validator' => $this->_validatorMock, + 'validator' => $validatorMock, 'authorization' => $this->_aclMock, 'scopeConfig' => $this->_scopeConfigMock, 'menuFactory' => $this->_menuFactoryMock, @@ -99,8 +97,7 @@ protected function setUp() public function testGetUrlWithEmptyActionReturnsHashSign() { $this->_params['action'] = ''; - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $item = $helper->getObject( + $item = $this->objectManager->getObject( \Magento\Backend\Model\Menu\Item::class, ['menuFactory' => $this->_menuFactoryMock, 'data' => $this->_params] ); @@ -129,8 +126,7 @@ public function testHasClickCallbackReturnsFalseIfItemHasAction() public function testHasClickCallbackReturnsTrueIfItemHasNoAction() { $this->_params['action'] = ''; - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $item = $helper->getObject( + $item = $this->objectManager->getObject( \Magento\Backend\Model\Menu\Item::class, ['menuFactory' => $this->_menuFactoryMock, 'data' => $this->_params] ); @@ -140,8 +136,7 @@ public function testHasClickCallbackReturnsTrueIfItemHasNoAction() public function testGetClickCallbackReturnsStoppingJsIfItemDoesntHaveAction() { $this->_params['action'] = ''; - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $item = $helper->getObject( + $item = $this->objectManager->getObject( \Magento\Backend\Model\Menu\Item::class, ['menuFactory' => $this->_menuFactoryMock, 'data' => $this->_params] ); @@ -218,15 +213,86 @@ public function testIsAllowedReturnsFalseIfResourceIsNotAvailable() public function testGetChildrenCreatesSubmenuOnFirstCall() { - $menuMock = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] - ); + $menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); $this->_menuFactoryMock->expects($this->once())->method('create')->will($this->returnValue($menuMock)); $this->_model->getChildren(); $this->_model->getChildren(); } + + /** + * @param array $data + * @param array $expected + * @dataProvider toArrayDataProvider + */ + public function testToArray(array $data, array $expected) + { + $menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); + $this->_menuFactoryMock->method('create')->will($this->returnValue($menuMock)); + $menuMock->method('toArray') + ->willReturn($data['sub_menu']); + + $model = $this->objectManager->getObject( + \Magento\Backend\Model\Menu\Item::class, + [ + 'authorization' => $this->_aclMock, + 'scopeConfig' => $this->_scopeConfigMock, + 'menuFactory' => $this->_menuFactoryMock, + 'urlModel' => $this->_urlModelMock, + 'moduleList' => $this->_moduleListMock, + 'moduleManager' => $this->_moduleManager, + 'data' => $data + ] + ); + $this->assertEquals($expected, $model->toArray()); + } + + /** + * @return array + */ + public function toArrayDataProvider() + { + return include __DIR__ . '/../_files/menu_item_data.php'; + } + + /** + * @param array $constructorData + * @param array $populateFromData + * @param array $expected + * @dataProvider populateFromArrayDataProvider + */ + public function testPopulateFromArray( + array $constructorData, + array $populateFromData, + array $expected + ) { + $menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); + $this->_menuFactoryMock->method('create')->willReturn($menuMock); + $menuMock->method('toArray') + ->willReturn(['submenuArray']); + + $model = $this->objectManager->getObject( + \Magento\Backend\Model\Menu\Item::class, + [ + 'authorization' => $this->_aclMock, + 'scopeConfig' => $this->_scopeConfigMock, + 'menuFactory' => $this->_menuFactoryMock, + 'urlModel' => $this->_urlModelMock, + 'moduleList' => $this->_moduleListMock, + 'moduleManager' => $this->_moduleManager, + 'data' => $constructorData + ] + ); + $model->populateFromArray($populateFromData); + $this->assertEquals($expected, $model->toArray()); + } + + /** + * @return array + */ + public function populateFromArrayDataProvider() + { + return include __DIR__ . '/../_files/menu_item_constructor_data.php'; + } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php b/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php index fc519ded8e860..7d972aa2792e1 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php @@ -1,6 +1,6 @@ objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_items['item1'] = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); $this->_items['item1']->expects($this->any())->method('getId')->will($this->returnValue('item1')); @@ -35,7 +45,12 @@ protected function setUp() $this->_logger = $this->getMock(\Psr\Log\LoggerInterface::class); - $this->_model = new \Magento\Backend\Model\Menu($this->_logger); + $this->_model = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger + ] + ); } public function testAdd() @@ -53,7 +68,7 @@ public function testAddDoLogAddAction() public function testAddToItem() { - $subMenu = $this->getMock(\Magento\Backend\Model\Menu::class, [], [$this->_logger]); + $subMenu = $this->getMockBuilder(\Magento\Backend\Model\Menu::class)->disableOriginalConstructor()->getMock(); $subMenu->expects($this->once())->method("add")->with($this->_items['item2']); $this->_items['item1']->expects($this->once())->method("getChildren")->will($this->returnValue($subMenu)); @@ -101,19 +116,29 @@ public function testGet() public function testGetRecursive() { - $menu1 = new \Magento\Backend\Model\Menu($this->_logger); - $menu2 = new \Magento\Backend\Model\Menu($this->_logger); + $menuOne = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger + ] + ); + $menuTwo = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger + ] + ); $this->_items['item1']->expects($this->any())->method('hasChildren')->will($this->returnValue(true)); - $this->_items['item1']->expects($this->any())->method('getChildren')->will($this->returnValue($menu1)); + $this->_items['item1']->expects($this->any())->method('getChildren')->will($this->returnValue($menuOne)); $this->_model->add($this->_items['item1']); $this->_items['item2']->expects($this->any())->method('hasChildren')->will($this->returnValue(true)); - $this->_items['item2']->expects($this->any())->method('getChildren')->will($this->returnValue($menu2)); - $menu1->add($this->_items['item2']); + $this->_items['item2']->expects($this->any())->method('getChildren')->will($this->returnValue($menuTwo)); + $menuOne->add($this->_items['item2']); $this->_items['item3']->expects($this->any())->method('hasChildren')->will($this->returnValue(false)); - $menu2->add($this->_items['item3']); + $menuTwo->add($this->_items['item3']); $this->assertEquals($this->_items['item1'], $this->_model->get('item1')); $this->assertEquals($this->_items['item2'], $this->_model->get('item2')); @@ -126,11 +151,7 @@ public function testMove() $this->_model->add($this->_items['item2']); $this->_model->add($this->_items['item3']); - $subMenu = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] - ); + $subMenu = $this->getMockBuilder(\Magento\Backend\Model\Menu::class)->disableOriginalConstructor()->getMock(); $subMenu->expects($this->once())->method("add")->with($this->_items['item3']); $this->_items['item1']->expects($this->once())->method("getChildren")->will($this->returnValue($subMenu)); @@ -179,11 +200,7 @@ public function testRemoveRemovesMenuItem() public function testRemoveRemovesMenuItemRecursively() { - $menuMock = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] - ); + $menuMock = $this->getMockBuilder(\Magento\Backend\Model\Menu::class)->disableOriginalConstructor()->getMock(); $menuMock->expects($this->once())->method('remove')->with($this->equalTo('item2')); $this->_items['item1']->expects($this->any())->method('hasChildren')->will($this->returnValue(true)); @@ -214,7 +231,12 @@ public function testReorderReordersItemOnItsLevel() { $this->_logger->expects($this->any())->method('log'); - $subMenu = new \Magento\Backend\Model\Menu($this->_logger); + $subMenu = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger + ] + ); $this->_items['item1']->expects($this->any())->method("hasChildren")->will($this->returnValue(true)); @@ -285,11 +307,11 @@ public function testMultipleIterationsWorkProperly() $items[] = $item->getId(); } - $items2 = []; + $itemsTwo = []; foreach ($this->_model as $item) { - $items2[] = $item->getId(); + $itemsTwo[] = $item->getId(); } - $this->assertEquals($items, $items2); + $this->assertEquals($items, $itemsTwo); } /** @@ -307,10 +329,10 @@ public function testNestedLoop() 'item3' => ['item1', 'item2', 'item3'], ]; $actual = []; - foreach ($this->_model as $valLoop1) { - $keyLevel1 = $valLoop1->getId(); - foreach ($this->_model as $valLoop2) { - $actual[$keyLevel1][] = $valLoop2->getId(); + foreach ($this->_model as $valLoopOne) { + $keyLevelOne = $valLoopOne->getId(); + foreach ($this->_model as $valLoopTwo) { + $actual[$keyLevelOne][] = $valLoopTwo->getId(); } } $this->assertEquals($expected, $actual); @@ -318,7 +340,45 @@ public function testNestedLoop() public function testSerialize() { - $this->assertNotEmpty($this->_model->serialize()); - $this->_model->add($this->_items['item1']); + $serializerMock = $this->getMock(SerializerInterface::class); + $serializerMock->expects($this->once()) + ->method('serialize') + ->with([['arrayData']]) + ->willReturn('serializedString'); + $menu = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger, + 'serializer' => $serializerMock, + ] + ); + $itemMock = $this->getMock(\Magento\Backend\Model\Menu\Item::class, [], [], '', false); + $itemMock->expects($this->any())->method('getId')->will($this->returnValue('item1')); + $itemMock->expects($this->once()) + ->method('toArray') + ->willReturn(['arrayData']); + $menu->add($itemMock); + $this->assertEquals('serializedString', $menu->serialize()); + } + + public function testUnserialize() + { + $serializerMock = $this->getMock(SerializerInterface::class); + $serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn([['unserializedData']]); + $menuItemFactoryMock = $this->getMock(Factory::class, [], [], '', false); + $menuItemFactoryMock->expects($this->once()) + ->method('create') + ->with(['unserializedData']); + $menu = $this->objectManagerHelper->getObject( + \Magento\Backend\Model\Menu::class, + [ + 'logger' => $this->_logger, + 'serializer' => $serializerMock, + 'menuItemFactory' => $menuItemFactoryMock, + ] + ); + $menu->unserialize('serializedString'); } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php b/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php index fa5b86df391c3..53817fc21a955 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->customerRepositoryMock = $this->getMockForAbstractClass( \Magento\Customer\Api\CustomerRepositoryInterface::class, [], @@ -197,13 +198,6 @@ protected function setUp() ); $this->quoteFactoryMock = $this->getMock(\Magento\Quote\Model\QuoteFactory::class, ['create'], [], '', false); - $this->cartManagementMock = $this->getMock( - \Magento\Quote\Api\CartManagementInterface::class, - [], - [], - '', - false - ); $this->quote = $this->getMock( \Magento\Backend\Model\Session\Quote::class, @@ -226,10 +220,6 @@ protected function setUp() 'quoteFactory' => $this->quoteFactoryMock ] ); - - $this->prepareObjectManager([ - [\Magento\Quote\Api\CartManagementInterface::class, $this->cartManagementMock] - ]); } /** @@ -416,19 +406,4 @@ public function getQuoteDataProvider() 'customer ids same' => [66, 66, 'never'], ]; } - - /** - * @param array $map - * @deprecated - */ - private function prepareObjectManager($map) - { - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('getInstance')->willReturnSelf(); - $objectManagerMock->expects($this->any())->method('get')->will($this->returnValueMap($map)); - $reflectionClass = new \ReflectionClass(\Magento\Framework\App\ObjectManager::class); - $reflectionProperty = $reflectionClass->getProperty('_instance'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($objectManagerMock); - } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Translate/Inline/ConfigTest.php b/app/code/Magento/Backend/Test/Unit/Model/Translate/Inline/ConfigTest.php index fbffdd4fdb16c..dbb03d249519f 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Translate/Inline/ConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Translate/Inline/ConfigTest.php @@ -1,6 +1,6 @@ _menuMock = $this->getMock( - \Magento\Backend\Model\Menu::class, - [], - [$this->getMock(\Psr\Log\LoggerInterface::class)] - ); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->_menuMock = $this->getMock(\Magento\Backend\Model\Menu::class, [], [], '', false); $this->_menuConfigMock = $this->getMock(\Magento\Backend\Model\Menu\Config::class, [], [], '', false); $this->_menuConfigMock->expects($this->any())->method('getMenu')->will($this->returnValue($this->_menuMock)); @@ -141,25 +137,21 @@ protected function setUp() false, false ); - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_encryptor = $this->getMock(\Magento\Framework\Encryption\Encryptor::class, null, [], '', false); - $this->_paramsResolverMock = $this->getMock( + $routeParamsResolver = $this->getMock(\Magento\Framework\Url\RouteParamsResolver::class, [], [], '', false); + $this->routeParamsResolverFactoryMock = $this->getMock( \Magento\Framework\Url\RouteParamsResolverFactory::class, [], [], '', false ); - $this->_paramsResolverMock->expects( - $this->any() - )->method( - 'create' - )->will( - $this->returnValue( - $this->getMock(\Magento\Framework\Url\RouteParamsResolver::class, [], [], '', false) - ) - ); - $this->_model = $helper->getObject( + $this->routeParamsResolverFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($routeParamsResolver); + /** @var HostChecker|\PHPUnit_Framework_MockObject_MockObject $hostCheckerMock */ + $hostCheckerMock = $this->getMock(HostChecker::class, [], [], '', false); + $this->_model = $objectManager->getObject( \Magento\Backend\Model\Url::class, [ 'scopeConfig' => $this->_scopeConfigMock, @@ -168,31 +160,10 @@ protected function setUp() 'menuConfig' => $this->_menuConfigMock, 'authSession' => $this->_authSessionMock, 'encryptor' => $this->_encryptor, - 'routeParamsResolverFactory' => $this->_paramsResolverMock + 'routeParamsResolverFactory' => $this->routeParamsResolverFactoryMock, + 'hostChecker' => $hostCheckerMock ] ); - $this->_paramsResolverMock->expects( - $this->any() - )->method( - 'create' - )->will( - $this->returnValue( - $this->getMock(\Magento\Framework\Url\RouteParamsResolver::class, [], [], '', false) - ) - ); - $this->_model = $helper->getObject( - \Magento\Backend\Model\Url::class, - [ - 'scopeConfig' => $this->_scopeConfigMock, - 'backendHelper' => $helperMock, - 'formKey' => $this->_formKey, - 'menuConfig' => $this->_menuConfigMock, - 'authSession' => $this->_authSessionMock, - 'encryptor' => $this->_encryptor, - 'routeParamsResolverFactory' => $this->_paramsResolverMock - ] - ); - $this->_requestMock = $this->getMock(\Magento\Framework\App\Request\Http::class, [], [], '', false); $this->_model->setRequest($this->_requestMock); } @@ -262,7 +233,7 @@ public function testGetAreaFrontName() [ 'backendHelper' => $helperMock, 'authSession' => $this->_authSessionMock, - 'routeParamsResolverFactory' => $this->_paramsResolverMock + 'routeParamsResolverFactory' => $this->routeParamsResolverFactoryMock ] ); $urlModel->getAreaFrontName(); diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/ConditionPoolTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/ConditionPoolTest.php new file mode 100644 index 0000000000000..46cea7665cc3b --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/ConditionPoolTest.php @@ -0,0 +1,65 @@ +objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new ObjectManager($this); + $this->conditionPool = $objectManager->getObject( + ConditionPool::class, + [ + 'conditions' => [ + 'condition-1' => 'Condition_1', + 'condition-2' => 'Condition_2', + ], + 'objectManager' => $this->objectManagerMock + ] + ); + } + + public function testGetCondition() + { + $this->objectManagerMock->expects($this->once()) + ->method('get') + ->with('Condition_1') + ->willReturn('Condition1Instance'); + $this->assertEquals( + 'Condition1Instance', + $this->conditionPool->getCondition('condition-1') + ); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testGetConditionUnknownCondition() + { + $this->conditionPool->getCondition('condition-3'); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/AclTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/AclTest.php index 820704c859769..9eb02e568439f 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/AclTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/AclTest.php @@ -1,28 +1,47 @@ authorizationMock = $this->getMockBuilder(\Magento\Framework\AuthorizationInterface::class) + $this->authorizationMock = $this->getMockBuilder(AuthorizationInterface::class) + ->getMock(); + $this->structureManager = $this->getMockBuilder(StructureManager::class) ->getMock(); - $this->model = new \Magento\Backend\Model\View\Layout\Filter\Acl($this->authorizationMock); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + Acl::class, + [ + 'authorization' => $this->authorizationMock + ] + ); + $objectManager->setBackwardCompatibleProperty($this->model, 'structureManager', $this->structureManager); } public function testFilterAclElements() @@ -86,28 +105,8 @@ public function testFilterAclElements() ] ); - $structureMock->expects($this->exactly(3)) - ->method('getChildren') - ->willReturnMap( - [ - ['element_2', ['element_2_child' => []]], - ['element_2_child', []], - ['element_3', []], - ] - ); - - $scheduledStructureMock->expects($this->exactly(3)) - ->method('unsetElement') - ->willReturnMap( - [ - ['element_2', null], - ['element_2_child', null], - ['element_3', null], - ] - ); - - $structureMock->expects($this->exactly(2)) - ->method('unsetElement') + $this->structureManager->expects($this->exactly(2)) + ->method('removeElement') ->willReturnMap( [ ['element_2', true, true], diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/ConditionTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/ConditionTest.php new file mode 100644 index 0000000000000..dacfbac3c4aa7 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/Filter/ConditionTest.php @@ -0,0 +1,139 @@ +structureManagerMock = $this->getMockBuilder(StructureManager::class) + ->getMock(); + $this->structureMock = $this->getMockBuilder(Structure::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scheduledStructureMock = $this->getMockBuilder(ScheduledStructure::class) + ->getMock(); + $this->conditionPoolMock = $this->getMockBuilder(ConditionPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->conditionMock = $this->getMockBuilder(ConditionInterface::class) + ->getMock(); + $objectManager = new ObjectManager($this); + + $this->filter = $objectManager->getObject( + Condition::class, + [ + 'structureManager' => $this->structureManagerMock, + 'conditionPool' => $this->conditionPoolMock + ] + ); + } + + private function getStructureData() + { + return [ + 'element_0' => [ + 0 => '', + 1 => [ + 'attributes' => [ + 'name' => 'element_0', + ], + ], + ], + 'element_1' => [ + 0 => '', + 1 => [ + 'attributes' => [ + 'name' => 'element_1', + 'condition' => 'TestCondition1', + ], + ], + ], + 'element_2' => [ + 0 => '', + 1 => [ + 'attributes' => [ + 'name' => 'element_2', + 'condition' => 'TestCondition2', + ], + ], + ], + 'element_3' => [ + 0 => '', + 1 => [ + 'attributes' => [ + 'name' => 'element_3', + 'acl' => 'acl_non_authorised', + ], + ], + ], + ]; + } + + public function testFilterElement() + { + $this->scheduledStructureMock->expects($this->once()) + ->method('getElements') + ->willReturn($this->getStructureData()); + $this->conditionPoolMock->expects($this->exactly(2)) + ->method('getCondition') + ->willReturnMap( + [ + ['TestCondition1', $this->conditionMock], + ['TestCondition2', $this->conditionMock] + ] + ); + $this->conditionMock->expects($this->at(0)) + ->method('validate') + ->willReturn(false); + $this->conditionMock->expects($this->at(1)) + ->method('validate') + ->willReturn(true); + $this->structureManagerMock->expects($this->once()) + ->method('removeElement') + ->with($this->scheduledStructureMock, $this->structureMock, 'element_1') + ->willReturn(true); + $this->assertTrue($this->filter->filterElement($this->scheduledStructureMock, $this->structureMock)); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/FilterTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/FilterTest.php new file mode 100644 index 0000000000000..0a51c9c1c56f3 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/FilterTest.php @@ -0,0 +1,65 @@ +someFilterMock = $this->getMockBuilder(FilterInterface::class) + ->getMock(); + $this->structureMock = $this->getMockBuilder(Structure::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scheduledStructureMock = $this->getMockBuilder(ScheduledStructure::class) + ->getMock(); + $objectManager = new ObjectManager($this); + $this->filter = $objectManager->getObject( + Filter::class, + [ + 'filters' => + [ + 'filter1' => $this->someFilterMock + ] + ] + ); + } + + public function testFilterElement() + { + $this->someFilterMock->expects($this->once()) + ->method('filterElement') + ->with($this->scheduledStructureMock, $this->structureMock); + $this->assertTrue($this->filter->filterElement($this->scheduledStructureMock, $this->structureMock)); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Layout/StructureManagerTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/StructureManagerTest.php new file mode 100644 index 0000000000000..a5a9055c0d03f --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Layout/StructureManagerTest.php @@ -0,0 +1,87 @@ +structureMock = $this->getMockBuilder(Structure::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scheduledStructureMock = $this->getMockBuilder(ScheduledStructure::class) + ->disableOriginalConstructor() + ->getMock(); + $this->structureManager = $objectManager->getObject(StructureManager::class); + } + + public function testRemoveElement() + { + $this->structureMock->expects($this->exactly(3)) + ->method('getChildren') + ->willReturnMap( + [ + [ + 'element-0', [ + 'element-1' => [], + 'element-2' => [] + ] + ], + [ + 'element-1', [] + ], + [ + 'element-2', [] + ] + ] + ); + $this->scheduledStructureMock->expects($this->exactly(3)) + ->method('unsetElement') + ->willReturnMap( + [ + ['element-0', true], + ['element-1', true], + ['element-2', true] + ] + ); + $this->structureMock->expects($this->once()) + ->method('unsetElement') + ->with('element-0'); + $this->assertTrue( + $this->structureManager->removeElement( + $this->scheduledStructureMock, + $this->structureMock, + 'element-0', + false + ) + ); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Model/View/Result/PageTest.php b/app/code/Magento/Backend/Test/Unit/Model/View/Result/PageTest.php index f88692029c159..3f1a6834f25a9 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/View/Result/PageTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/View/Result/PageTest.php @@ -1,6 +1,6 @@ [ + [], + [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + [ + 'parent_id' => null, + 'module_name' => 'Magento_Backend', + 'sort_index' => null, + 'depends_on_config' => 'system/config/isEnabled', + 'id' => 'item', + 'resource' => 'Magento_Config::config', + 'path' => '', + 'action' => '/system/config', + 'depends_on_module' => 'Magento_Backend', + 'tooltip' => 'Item tooltip', + 'title' => 'Item Title', + 'sub_menu' => null, + 'target' => null + ], + ], + 'data without submenu to constructor' => [ + [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => '5', + 'resource' => null, + 'path' => null, + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => null, + 'title' => null, + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => '5', + 'resource' => null, + 'path' => '', + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => '', + 'title' => null, + 'sub_menu' => ['submenuArray'], + 'target' => null + ], + ], + 'data with submenu to constructor' => [ + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => '5', + 'resource' => null, + 'path' => null, + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => null, + 'title' => null, + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => null, + 'resource' => null, + 'path' => '', + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => '', + 'title' => null, + 'sub_menu' => ['submenuArray'], + 'target' => null + ], + ] +]; diff --git a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php new file mode 100644 index 0000000000000..bc336aad5783d --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php @@ -0,0 +1,121 @@ + [ + [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + 'sub_menu' => null, + ], + [ + 'parent_id' => null, + 'module_name' => 'Magento_Backend', + 'sort_index' => null, + 'depends_on_config' => 'system/config/isEnabled', + 'id' => 'item', + 'resource' => 'Magento_Config::config', + 'path' => '', + 'action' => '/system/config', + 'depends_on_module' => 'Magento_Backend', + 'tooltip' => 'Item tooltip', + 'title' => 'Item Title', + 'sub_menu' => null, + 'target' => null + ] + ], + 'with submenu' => [ + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => '5', + 'resource' => null, + 'path' => null, + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => null, + 'title' => null, + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => '5', + 'resource' => null, + 'path' => null, + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => '', + 'title' => null, + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + 'target' => null + ] + ], + 'small set of data' => [ + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + ], + [ + 'parent_id' => '1', + 'module_name' => 'Magento_Module1', + 'sort_index' => '50', + 'depends_on_config' => null, + 'id' => null, + 'resource' => null, + 'path' => '', + 'action' => null, + 'depends_on_module' => null, + 'tooltip' => '', + 'title' => null, + 'sub_menu' => [ + 'id' => 'item', + 'title' => 'Item Title', + 'action' => '/system/config', + 'resource' => 'Magento_Config::config', + 'depends_on_module' => 'Magento_Backend', + 'depends_on_config' => 'system/config/isEnabled', + 'tooltip' => 'Item tooltip', + ], + 'target' => null + ] + ] +]; diff --git a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.php b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.php index ad86dea88ef08..c5213cdab9d87 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.php +++ b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.xml b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.xml index 55ed5a90d8025..e7ac5aec547d2 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.xml +++ b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_merged.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/Test/Unit/Setup/ConfigOptionsListTest.php b/app/code/Magento/Backend/Test/Unit/Setup/ConfigOptionsListTest.php index d7a8de1012b82..dd90832aa38b4 100644 --- a/app/code/Magento/Backend/Test/Unit/Setup/ConfigOptionsListTest.php +++ b/app/code/Magento/Backend/Test/Unit/Setup/ConfigOptionsListTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/etc/adminhtml/di.xml b/app/code/Magento/Backend/etc/adminhtml/di.xml index fcf2cc02e9fb9..9488d35adcc87 100644 --- a/app/code/Magento/Backend/etc/adminhtml/di.xml +++ b/app/code/Magento/Backend/etc/adminhtml/di.xml @@ -1,7 +1,7 @@ @@ -141,4 +141,12 @@ + + + + Magento\Config\Model\Config\Structure\ElementVisibilityInterface::HIDDEN + Magento\Config\Model\Config\Structure\ElementVisibilityInterface::DISABLED + + + diff --git a/app/code/Magento/Backend/etc/adminhtml/menu.xml b/app/code/Magento/Backend/etc/adminhtml/menu.xml index 1a754291cb47c..d7d57b7953c77 100644 --- a/app/code/Magento/Backend/etc/adminhtml/menu.xml +++ b/app/code/Magento/Backend/etc/adminhtml/menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/etc/adminhtml/routes.xml b/app/code/Magento/Backend/etc/adminhtml/routes.xml index 232ac5222daea..2f3857c91c5c0 100644 --- a/app/code/Magento/Backend/etc/adminhtml/routes.xml +++ b/app/code/Magento/Backend/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 0eb01d6a252ea..d33abf75acd56 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ @@ -16,7 +16,11 @@ -
    + +
    advanced Magento_Backend::advanced diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml index 459dd377e36ba..1d347c7471725 100644 --- a/app/code/Magento/Backend/etc/config.xml +++ b/app/code/Magento/Backend/etc/config.xml @@ -1,7 +1,7 @@ @@ -11,6 +11,9 @@ + + 1 + diff --git a/app/code/Magento/Backend/etc/crontab.xml b/app/code/Magento/Backend/etc/crontab.xml index 0c7e18e977549..4f6450a7226ae 100644 --- a/app/code/Magento/Backend/etc/crontab.xml +++ b/app/code/Magento/Backend/etc/crontab.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/etc/di.xml b/app/code/Magento/Backend/etc/di.xml index 8b52d08da48fb..8894f2293fa35 100644 --- a/app/code/Magento/Backend/etc/di.xml +++ b/app/code/Magento/Backend/etc/di.xml @@ -1,7 +1,7 @@ @@ -14,6 +14,7 @@ + @@ -180,37 +181,36 @@ - + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + + - - - design/theme/theme_id - theme - Magento\Theme\Model\Design\Backend\Theme - true - - - design/theme/ua_regexp - desing_rule - Magento\Theme\Model\Design\Backend\Exceptions - - - design/pagination/pagination_frame - other_settings/pagination - - - design/pagination/pagination_frame_skip - other_settings/pagination - - - design/pagination/anchor_text_for_previous - other_settings/pagination - - - design/pagination/anchor_text_for_next - other_settings/pagination - + + Magento\Backend\Model\View\Layout\Filter\Acl + Magento\Backend\Model\View\Layout\Filter\Condition + + + Magento\Backend\Block\MenuItemChecker + Magento\Backend\Block\AnchorRenderer + + diff --git a/app/code/Magento/Backend/etc/menu.xsd b/app/code/Magento/Backend/etc/menu.xsd index 05df67a5e2bbd..f7a22103e2e90 100644 --- a/app/code/Magento/Backend/etc/menu.xsd +++ b/app/code/Magento/Backend/etc/menu.xsd @@ -1,7 +1,7 @@ @@ -31,6 +31,7 @@ + @@ -51,6 +52,7 @@ + diff --git a/app/code/Magento/Backend/etc/module.xml b/app/code/Magento/Backend/etc/module.xml index db6b19407c73b..b7f188e78d34e 100644 --- a/app/code/Magento/Backend/etc/module.xml +++ b/app/code/Magento/Backend/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/etc/webapi.xml b/app/code/Magento/Backend/etc/webapi.xml index d01bbafa8e676..14d4dccc78cbe 100644 --- a/app/code/Magento/Backend/etc/webapi.xml +++ b/app/code/Magento/Backend/etc/webapi.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index 252269ba97ffb..9144df6c8f7d9 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -250,7 +250,7 @@ Minute,Minute "JavaScript may be disabled in your browser.","JavaScript may be disabled in your browser." "To use this website you must first enable JavaScript in your browser.","To use this website you must first enable JavaScript in your browser." "This is only a demo store. You can browse and place orders, but nothing will be processed.","This is only a demo store. You can browse and place orders, but nothing will be processed." -"Report Bugs","Report Bugs" +"Report a Bug","Report a Bug" "Store View:","Store View:" "Stores Configuration","Stores Configuration" "Please confirm scope switching. All data that hasn\'t been saved will be lost.","Please confirm scope switching. All data that hasn\'t been saved will be lost." diff --git a/app/code/Magento/Backend/registration.php b/app/code/Magento/Backend/registration.php index 5e9d9fe4dabe7..fac71f545151f 100644 --- a/app/code/Magento/Backend/registration.php +++ b/app/code/Magento/Backend/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_auth_login.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_auth_login.xml index d8462aeedfa1a..bb7370b3317a4 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_auth_login.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_auth_login.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml index 3e61fec077c6e..e4f6fbea1cd29 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml @@ -1,7 +1,7 @@ @@ -23,10 +23,12 @@ Enable adminhtml/*/massEnable + Magento\Backend\Block\Cache\Grid\Massaction\ProductionModeVisibilityChecker Disable adminhtml/*/massDisable + Magento\Backend\Block\Cache\Grid\Massaction\ProductionModeVisibilityChecker Refresh @@ -48,6 +50,7 @@ 180 left 0 + true @@ -57,6 +60,7 @@ text left 0 + true diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml index 8edb476a2b555..77ba8f6f39824 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersmost.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersmost.xml index 39b3db77d505e..31b3ff2c44bc1 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersmost.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersmost.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersnewest.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersnewest.xml index ce83aa64faafb..ce4e20140eb54 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersnewest.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_customersnewest.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml index 8b36caac55b82..b353b81aa1b7c 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_productsviewed.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_productsviewed.xml index d55194f4dbb43..4621d83dc6753 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_productsviewed.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_productsviewed.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_denied.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_denied.xml index e8754242dfd2f..fac9407cc3298 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_denied.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_denied.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_noroute.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_noroute.xml index 4872a39a16e45..598bdf490e8a5 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_noroute.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_noroute.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_account_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_account_index.xml index 581f6d2ee5972..dfc27d5e6b5e0 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_account_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_account_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_edit.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_edit.xml index 4f5d3a778a120..91469c51e06c9 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_edit.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid.xml index 4a0c8a711f5ea..bda7f3883adce 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml index 3d2c4dff37228..a39aee9813286 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_index.xml index 8ae928a3cadcf..7412c983cff8b 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml index 320ce474bc392..0521a87831663 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_index.xml index 64d7968bd1772..2abd830f5fa37 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/default.xml b/app/code/Magento/Backend/view/adminhtml/layout/default.xml index 9db902fb13294..ea98fd70740aa 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/default.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/default.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/editor.xml b/app/code/Magento/Backend/view/adminhtml/layout/editor.xml index 9109e54ac357b..2c34667c0502e 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/editor.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/editor.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/empty.xml b/app/code/Magento/Backend/view/adminhtml/layout/empty.xml index 7509438df2553..e01b48cc71b11 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/empty.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/empty.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/formkey.xml b/app/code/Magento/Backend/view/adminhtml/layout/formkey.xml index 50b1784b33210..6ba9743703ed4 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/formkey.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/formkey.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/overlay_popup.xml b/app/code/Magento/Backend/view/adminhtml/layout/overlay_popup.xml index 67304406b201b..8a48fcca66c0e 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/overlay_popup.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/overlay_popup.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/layout/popup.xml b/app/code/Magento/Backend/view/adminhtml/layout/popup.xml index 825094937cd9d..dd65940af81e5 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/popup.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/popup.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/requirejs-config.js b/app/code/Magento/Backend/view/adminhtml/requirejs-config.js index 9c1350ea268cb..338490f3f4413 100644 --- a/app/code/Magento/Backend/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Backend/view/adminhtml/requirejs-config.js @@ -1,8 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -/*eslint no-unused-vars: 0*/ + var config = { map: { '*': { 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 7ff08dcc32422..32910c9978d2d 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 @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml index 0968047053bc5..1716af3ec14c4 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml index ab29376b3b573..e95b063599daa 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml @@ -1,6 +1,6 @@
    -
    + getChildHtml(); ?> + + + value="escapeHtmlAttr($block->getFormKey()); ?>"/>
    diff --git a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml index e93b143e039c8..aeac3fd33248c 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml index 987afc07df489..b4e7c2d7f50a8 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml @@ -1,6 +1,6 @@ getBugreportUrl()): ?> - + diff --git a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml index c712eeef6db74..1b0a1d9a662ef 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml index 3b5748da54823..2cdb9f451a86f 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml index 52e4d895c1dc1..060ee67e9a7b2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml @@ -1,6 +1,6 @@ getExportButtonHtml() ?> -
    \ No newline at end of file +
    diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml index 62ff8d2752362..838e22f16493a 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml index c72e5f3c9079e..c299f217c8df7 100644 --- a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml +++ b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/bootstrap/editor.js b/app/code/Magento/Backend/view/adminhtml/web/js/bootstrap/editor.js index 08ff14f84f660..9e57f2fd5bd4b 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/bootstrap/editor.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/bootstrap/editor.js @@ -1,9 +1,10 @@ /** * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + require([ - "Magento_Variable/variables", - "mage/adminhtml/browser" -]); \ No newline at end of file + 'Magento_Variable/variables', + 'mage/adminhtml/browser' +]); diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js index 18f44da26109a..fe7c8dd7cb800 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js @@ -1,6 +1,6 @@ /** * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*global byteConvert*/ diff --git a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/cells/action-delete.html b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/cells/action-delete.html index 44c460825b45d..e60fc8c2c238e 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/cells/action-delete.html +++ b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/cells/action-delete.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html index 3a38ced51f7ed..8714e443faf28 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html +++ b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backend/view/adminhtml/web/template/form/element/helper/fallback-reset-link.html b/app/code/Magento/Backend/view/adminhtml/web/template/form/element/helper/fallback-reset-link.html index 25b584f89f9fa..f788dbb6fbe20 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/template/form/element/helper/fallback-reset-link.html +++ b/app/code/Magento/Backend/view/adminhtml/web/template/form/element/helper/fallback-reset-link.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backup/Block/Adminhtml/Backup.php b/app/code/Magento/Backup/Block/Adminhtml/Backup.php index 354dca87a79a9..0aa4f8833e9ea 100644 --- a/app/code/Magento/Backup/Block/Adminhtml/Backup.php +++ b/app/code/Magento/Backup/Block/Adminhtml/Backup.php @@ -1,6 +1,6 @@ _view->loadLayout(); - $this->_view->renderLayout(); + return $this->resultFactory->create(ResultFactory::TYPE_PAGE); } } diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php index d845a28569f63..f784e507e96b1 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/adminhtml/menu.xml b/app/code/Magento/Backup/etc/adminhtml/menu.xml index 812b1cb9d0df8..32c2936697fac 100644 --- a/app/code/Magento/Backup/etc/adminhtml/menu.xml +++ b/app/code/Magento/Backup/etc/adminhtml/menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/adminhtml/routes.xml b/app/code/Magento/Backup/etc/adminhtml/routes.xml index 3e0e606439eed..232c0ca0f9d6a 100644 --- a/app/code/Magento/Backup/etc/adminhtml/routes.xml +++ b/app/code/Magento/Backup/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/adminhtml/system.xml b/app/code/Magento/Backup/etc/adminhtml/system.xml index 69e15030ad6a7..325395826df15 100644 --- a/app/code/Magento/Backup/etc/adminhtml/system.xml +++ b/app/code/Magento/Backup/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/crontab.xml b/app/code/Magento/Backup/etc/crontab.xml index a0a1fdbdf2a15..150751eb94a1f 100644 --- a/app/code/Magento/Backup/etc/crontab.xml +++ b/app/code/Magento/Backup/etc/crontab.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/di.xml b/app/code/Magento/Backup/etc/di.xml index f9371f4a249bf..fc9c5bb2ff025 100644 --- a/app/code/Magento/Backup/etc/di.xml +++ b/app/code/Magento/Backup/etc/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/etc/module.xml b/app/code/Magento/Backup/etc/module.xml index 9edced27b2247..9f4fe8da7ac1d 100644 --- a/app/code/Magento/Backup/etc/module.xml +++ b/app/code/Magento/Backup/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/registration.php b/app/code/Magento/Backup/registration.php index c158dddf70a65..d429ad8208552 100644 --- a/app/code/Magento/Backup/registration.php +++ b/app/code/Magento/Backup/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_grid.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_grid.xml index 1f6e1cbec7f10..03b4aa4a7f4ad 100644 --- a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_grid.xml +++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_grid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_index.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_index.xml index 42c3d766b6997..242534d006e37 100644 --- a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_index.xml +++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml index c3ac897edd8e5..597651f8329e3 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml index 0fff82609ed11..ace0931b60428 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml @@ -1,6 +1,6 @@ orderPlace = $orderPlace; + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -58,6 +70,7 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ return $resultRedirect->setPath('checkout/onepage/success', ['_secure' => true]); } catch (\Exception $e) { + $this->logger->critical($e); $this->messageManager->addExceptionMessage($e, $e->getMessage()); } diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php index 1b6c3a3ab5a91..a6d2234f26aba 100644 --- a/app/code/Magento/Braintree/Controller/Paypal/Review.php +++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php @@ -1,6 +1,6 @@ serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(Json::class); + } + /** * Get list of available dynamic descriptors keys * @var array @@ -45,7 +72,7 @@ class Config extends \Magento\Payment\Gateway\Config\Config */ public function getCountrySpecificCardTypeConfig() { - $countriesCardTypes = unserialize($this->getValue(self::KEY_COUNTRY_CREDIT_CARD)); + $countriesCardTypes = $this->serializer->unserialize($this->getValue(self::KEY_COUNTRY_CREDIT_CARD)); return is_array($countriesCardTypes) ? $countriesCardTypes : []; } diff --git a/app/code/Magento/Braintree/Gateway/Config/PayPal/Config.php b/app/code/Magento/Braintree/Gateway/Config/PayPal/Config.php index f94c6abfd773b..3f34afd0717db 100644 --- a/app/code/Magento/Braintree/Gateway/Config/PayPal/Config.php +++ b/app/code/Magento/Braintree/Gateway/Config/PayPal/Config.php @@ -1,6 +1,6 @@ paymentTokenFactory = $paymentTokenFactory; $this->paymentExtensionFactory = $paymentExtensionFactory; $this->config = $config; $this->subjectReader = $subjectReader; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); } /** @@ -133,7 +143,7 @@ private function getExpirationDate(Transaction $transaction) */ private function convertDetailsToJSON($details) { - $json = \Zend_Json::encode($details); + $json = $this->serializer->serialize($details); return $json ? $json : '{}'; } diff --git a/app/code/Magento/Braintree/Gateway/Response/VoidHandler.php b/app/code/Magento/Braintree/Gateway/Response/VoidHandler.php index 5fc173af704a8..c3534d4a906e4 100644 --- a/app/code/Magento/Braintree/Gateway/Response/VoidHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/VoidHandler.php @@ -1,6 +1,6 @@ collectionFactory = $factory; $this->countryConfig = $countryConfig; } diff --git a/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php index 12e7cd8f532cc..c53d9e3f5ba8c 100644 --- a/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php +++ b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php @@ -1,6 +1,6 @@ mathRandom = $mathRandom; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(Json::class); parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); } @@ -68,7 +78,7 @@ public function beforeSave() $result[$country] = $data['cc_types']; } } - $this->setValue(serialize($result)); + $this->setValue($this->serializer->serialize($result)); return $this; } @@ -79,7 +89,7 @@ public function beforeSave() */ public function afterLoad() { - $value = unserialize($this->getValue()); + $value = $this->serializer->unserialize($this->getValue()); if (is_array($value)) { $value = $this->encodeArrayFieldValue($value); $this->setValue($value); diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/AbstractHelper.php b/app/code/Magento/Braintree/Model/Paypal/Helper/AbstractHelper.php index ad72fcf8a424b..0a5583d465f68 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/AbstractHelper.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/AbstractHelper.php @@ -1,6 +1,6 @@ componentFactory = $componentFactory; + $this->urlBuilder = $urlBuilder; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getComponentForToken(PaymentTokenInterface $paymentToken) + { + $data = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + $data['icon'] = $this->config->getPayPalIcon(); + $component = $this->componentFactory->create( + [ + 'config' => [ + 'code' => PayPalConfigProvider::PAYPAL_VAULT_CODE, + 'nonceUrl' => $this->getNonceRetrieveUrl(), + TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data, + TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(), + 'template' => 'Magento_Braintree::form/paypal/vault.phtml' + ], + 'name' => Template::class + ] + ); + + return $component; + } + + /** + * Get url to retrieve payment method nonce + * @return string + */ + private function getNonceRetrieveUrl() + { + return $this->urlBuilder->getUrl(ConfigProvider::CODE . '/payment/getnonce', ['_secure' => true]); + } +} diff --git a/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php b/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php index 6cfc96ea23d0d..b2c6b53e2538a 100644 --- a/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php @@ -1,6 +1,6 @@ componentFactory->create( [ 'config' => [ + 'code' => ConfigProvider::CC_VAULT_CODE, 'nonceUrl' => $this->getNonceRetrieveUrl(), TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data, TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(), diff --git a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php index 76788c3c14510..215f51627f194 100644 --- a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php @@ -1,6 +1,6 @@ fieldDataConverterFactory = $fieldDataConverterFactory; + $this->queryModifierFactory = $queryModifierFactory; + } + + /** + * Upgrades data for Braintree module + * + * @param ModuleDataSetupInterface $setup + * @param ModuleContextInterface $context + * @return void + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $this->convertSerializedDataToJson($setup); + } + } + + /** + * Upgrade data to version 2.0.1, converts row data in the core_config_data table that uses the path + * payment/braintree/countrycreditcard from serialized to JSON + * + * @param ModuleDataSetupInterface $setup + * @return void + */ + private function convertSerializedDataToJson(ModuleDataSetupInterface $setup) + { + $fieldDataConverter = $this->fieldDataConverterFactory->create( + \Magento\Framework\DB\DataConverter\SerializedToJson::class + ); + + $queryModifier = $this->queryModifierFactory->create( + 'in', + [ + 'values' => [ + 'path' => ['payment/braintree/countrycreditcard'] + ] + ] + ); + + $fieldDataConverter->convert( + $setup->getConnection(), + $setup->getTable('core_config_data'), + 'config_id', + 'value', + $queryModifier + ); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php b/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php index e6c7ce59d0e29..4d2b2cba3faaf 100644 --- a/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php @@ -1,6 +1,6 @@ method('getMessageManager') ->willReturn($this->messageManagerMock); + $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->placeOrder = new PlaceOrder( $contextMock, $this->configMock, $this->checkoutSessionMock, - $this->orderPlaceMock + $this->orderPlaceMock, + $this->loggerMock ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php index a5cdaf3e35b70..88f0c4c11c829 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php @@ -1,6 +1,6 @@ scopeConfigMock = $this->getMock(ScopeConfigInterface::class); + $this->serializerMock = $this->getMock(Json::class); - $this->model = new Config($this->scopeConfigMock, self::METHOD_CODE); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + Config::class, + [ + 'scopeConfig' => $this->scopeConfigMock, + 'methodCode' => self::METHOD_CODE, + 'serializer' => $this->serializerMock + ] + ); } /** - * @param string $value + * @param string $encodedValue + * @param string|array $value * @param array $expected * @dataProvider getCountrySpecificCardTypeConfigDataProvider */ - public function testGetCountrySpecificCardTypeConfig($value, $expected) + public function testGetCountrySpecificCardTypeConfig($encodedValue, $value, array $expected) { $this->scopeConfigMock->expects(static::once()) ->method('getValue') ->with($this->getPath(Config::KEY_COUNTRY_CREDIT_CARD), ScopeInterface::SCOPE_STORE, null) + ->willReturn($encodedValue); + + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with($encodedValue) ->willReturn($value); static::assertEquals( @@ -58,11 +80,13 @@ public function testGetCountrySpecificCardTypeConfig($value, $expected) public function getCountrySpecificCardTypeConfigDataProvider() { return [ - [ - serialize(['GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB']]), + 'valid data' => [ + '{"GB":["VI","AE"],"US":["DI","JCB"]}', + ['GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB']], ['GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB']] ], - [ + 'non-array value' => [ + '""', '', [] ] @@ -146,12 +170,20 @@ public function getCcTypesMapperDataProvider() /** * @covers \Magento\Braintree\Gateway\Config\Config::getCountryAvailableCardTypes * @dataProvider getCountrySpecificCardTypeConfigDataProvider + * @param string $encodedData + * @param string|array $data + * @param array $countryData */ - public function testCountryAvailableCardTypes($data, $countryData) + public function testCountryAvailableCardTypes($encodedData, $data, array $countryData) { $this->scopeConfigMock->expects(static::any()) ->method('getValue') ->with($this->getPath(Config::KEY_COUNTRY_CREDIT_CARD), ScopeInterface::SCOPE_STORE, null) + ->willReturn($encodedData); + + $this->serializerMock->expects($this->any()) + ->method('unserialize') + ->with($encodedData) ->willReturn($data); foreach ($countryData as $countryId => $types) { diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php index 02192c819daf5..f4056c8abb3f3 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php @@ -1,6 +1,6 @@ method('getCctypesMapper') ->willReturn($mapperArray); + $this->serializer = $this->getMock( + \Magento\Framework\Serialize\Serializer\Json::class, + [], + [], + '', + false + ); + $this->paymentHandler = new VaultDetailsHandler( $this->paymentTokenFactory, $this->paymentExtensionFactory, $this->config, - $this->subjectReader + $this->subjectReader, + $this->serializer ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php index f0ec710e13827..51971e077f91a 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php @@ -1,6 +1,6 @@ resourceMock = $this->getMockForAbstractClass(AbstractResource::class); $this->mathRandomMock = $this->getMockBuilder(Random::class) ->disableOriginalConstructor() ->getMock(); + $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class); $this->objectManager = new ObjectManager($this); $this->model = $this->objectManager->getObject( @@ -50,18 +56,28 @@ protected function setUp() [ 'mathRandom' => $this->mathRandomMock, 'resource' => $this->resourceMock, + 'serializer' => $this->serializerMock ] ); } /** * @dataProvider beforeSaveDataProvider + * @param array $value + * @param array $expectedValue + * @param string $encodedValue */ - public function testBeforeSave($value, $expectedValue) + public function testBeforeSave(array $value, array $expectedValue, $encodedValue) { $this->model->setValue($value); + + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->with($expectedValue) + ->willReturn($encodedValue); + $this->model->beforeSave(); - $this->assertEquals($expectedValue, $this->model->getValue()); + $this->assertEquals($encodedValue, $this->model->getValue()); } /** @@ -73,11 +89,13 @@ public function beforeSaveDataProvider() return [ 'empty_value' => [ 'value' => [], - 'expected' => serialize([]), + 'expected' => [], + 'encoded' => '[]' ], 'not_array' => [ 'value' => ['US'], - 'expected' => serialize([]), + 'expected' => [], + 'encoded' => '[]' ], 'array_with_invalid_format' => [ 'value' => [ @@ -85,7 +103,8 @@ public function beforeSaveDataProvider() 'country_id' => 'US', ], ], - 'expected' => serialize([]), + 'expected' => [], + 'encoded' => '[]' ], 'array_with_two_countries' => [ 'value' => [ @@ -99,12 +118,11 @@ public function beforeSaveDataProvider() ], '__empty' => "", ], - 'expected' => serialize( - [ - 'AF' => ['AE', 'VI'], - 'US' => ['AE', 'VI', 'MA'], - ] - ), + 'expected' => [ + 'AF' => ['AE', 'VI'], + 'US' => ['AE', 'VI', 'MA'], + ], + 'encoded' => '{"AF":["AE","VI"],"US":["AE","VI","MA"]}' ], 'array_with_two_same_countries' => [ 'value' => [ @@ -122,29 +140,38 @@ public function beforeSaveDataProvider() ], '__empty' => "", ], - 'expected' => serialize( - [ - 'AF' => ['AE', 'VI'], - 'US' => ['AE', 'VI', 'MA', 'OT'], - ] - ), + 'expected' => [ + 'AF' => ['AE', 'VI'], + 'US' => ['AE', 'VI', 'MA', 'OT'], + ], + 'encoded' => '{"AF":["AE","VI"],"US":["AE","VI","MA","OT"]}' ], ]; } /** * @dataProvider afterLoadDataProvider + * @param string $encodedValue + * @param array|null $value + * @param array $hashData + * @param array|null $expected */ - public function testAfterLoad($value, $hashData, $expected) + public function testAfterLoad($encodedValue, $value, array $hashData, $expected) { - $this->model->setValue($value); + $this->model->setValue($encodedValue); $index = 0; foreach ($hashData as $hash) { $this->mathRandomMock->expects(static::at($index)) ->method('getUniqueHash') ->willReturn($hash); - $index ++; + $index++; } + + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with($encodedValue) + ->willReturn($value); + $this->model->afterLoad(); $this->assertEquals($expected, $this->model->getValue()); } @@ -157,34 +184,35 @@ public function afterLoadDataProvider() { return [ 'empty' => [ - 'value' => serialize([]), + 'encoded' => '[]', + 'value' => [], 'randomHash' => [], - 'expected' => [], + 'expected' => [] ], 'null' => [ + 'encoded' => '', 'value' => null, 'randomHash' => [], - 'expected' => null, + 'expected' => null ], - 'valid_data' => [ - 'value' => serialize( - [ - 'US' => ['AE', 'VI', 'MA'], - 'AF' => ['AE', 'MA'], - ] - ), + 'valid data' => [ + 'encoded' => '{"US":["AE","VI","MA"],"AF":["AE","MA"]}', + 'value' => [ + 'US' => ['AE', 'VI', 'MA'], + 'AF' => ['AE', 'MA'] + ], 'randomHash' => ['hash_1', 'hash_2'], 'expected' => [ 'hash_1' => [ 'country_id' => 'US', - 'cc_types' => ['AE', 'VI', 'MA'], + 'cc_types' => ['AE', 'VI', 'MA'] ], 'hash_2' => [ 'country_id' => 'AF', - 'cc_types' => ['AE', 'MA'], - ], + 'cc_types' => ['AE', 'MA'] + ] ] - ], + ] ]; } } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php index 3beaf139e5bc5..ccb8e7e8f6e17 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php @@ -1,6 +1,6 @@ componentFactory = $this->getMockBuilder(TokenUiComponentInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->urlBuilder = $this->getMock(UrlInterface::class); + + $this->config = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getPayPalIcon']) + ->getMock(); + + $this->tokenUiComponentProvider = new TokenUiComponentProvider( + $this->componentFactory, + $this->urlBuilder, + $this->config + ); + } + + /** + * @covers \Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider::getComponentForToken + */ + public function testGetComponentForToken() + { + $nonceUrl = 'https://payment/adminhtml/nonce/url'; + $payerEmail = 'john.doe@test.com'; + $icon = [ + 'url' => 'https://payment/adminhtml/icon.png', + 'width' => 48, + 'height' => 32 + ]; + + $expected = [ + 'code' => 'vault', + 'nonceUrl' => $nonceUrl, + 'details' => [ + 'payerEmail' => $payerEmail, + 'icon' => $icon + ], + 'template' => 'vault.phtml' + ]; + + $this->config->expects(static::once()) + ->method('getPayPalIcon') + ->willReturn($icon); + + $paymentToken = $this->getMock(PaymentTokenInterface::class); + $paymentToken->expects(static::once()) + ->method('getTokenDetails') + ->willReturn('{"payerEmail":" ' . $payerEmail . '"}'); + $paymentToken->expects(static::once()) + ->method('getPublicHash') + ->willReturn('cmk32dl21l'); + + $this->urlBuilder->expects(static::once()) + ->method('getUrl') + ->willReturn($nonceUrl); + + $tokenComponent = $this->getMock(TokenUiComponentInterface::class); + $tokenComponent->expects(static::once()) + ->method('getConfig') + ->willReturn($expected); + + $this->componentFactory->expects(static::once()) + ->method('create') + ->willReturn($tokenComponent); + + $component = $this->tokenUiComponentProvider->getComponentForToken($paymentToken); + static::assertEquals($tokenComponent, $component); + static::assertEquals($expected, $component->getConfig()); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php index d1665c71804cf..d39cd32425013 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php @@ -1,16 +1,16 @@ 'vault', 'nonceUrl' => $nonceUrl, 'details' => [ 'type' => $type, diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php index 04846f369eba9..1adc43a1ecd28 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index f252b977f20bd..c91b4edbd2e70 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -1,7 +1,7 @@ @@ -47,6 +47,7 @@ Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider + Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider
    diff --git a/app/code/Magento/Braintree/etc/adminhtml/menu.xml b/app/code/Magento/Braintree/etc/adminhtml/menu.xml index ed73aa20cdb6f..43d12aa0bb995 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/menu.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/adminhtml/routes.xml b/app/code/Magento/Braintree/etc/adminhtml/routes.xml index 698664f02e6a3..0991aef948541 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/routes.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index e4f4e11983892..ef3cdc3f51256 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index 095a8419c8529..eaa233da109ce 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -1,7 +1,7 @@ @@ -71,7 +71,8 @@ BraintreePayPalVaultFacade - Vault Token (Braintree PayPal) + Stored Accounts (Braintree PayPal) + 1 diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 849a3039fc361..6f596cc4a8daf 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -1,7 +1,7 @@ @@ -22,6 +22,7 @@ Magento\Braintree\Model\Ui\PayPal\ConfigProvider::PAYPAL_CODE BraintreePayPalInfo BraintreePayPalValueHandlerPool + BraintreePayPalValidatorPool BraintreePayPalCommandPool @@ -364,7 +365,7 @@ - + @@ -451,7 +452,7 @@ - + @@ -474,7 +475,7 @@ - + Magento\Braintree\Gateway\Config\Config @@ -487,6 +488,22 @@ + + + + + + Magento\Braintree\Gateway\Config\PayPal\Config + + + + + + BraintreePayPalCountryValidator + + + + @@ -527,4 +544,16 @@ + + + + 1 + 1 + 1 + 1 + 1 + 1 + + + diff --git a/app/code/Magento/Braintree/etc/events.xml b/app/code/Magento/Braintree/etc/events.xml index 6d76a626d15bd..2bf95bdbad178 100644 --- a/app/code/Magento/Braintree/etc/events.xml +++ b/app/code/Magento/Braintree/etc/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml index cdd56e236a72b..781f985b4b3a8 100644 --- a/app/code/Magento/Braintree/etc/frontend/di.xml +++ b/app/code/Magento/Braintree/etc/frontend/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/frontend/events.xml b/app/code/Magento/Braintree/etc/frontend/events.xml index df1db1420b5a1..e1bff1a20b238 100644 --- a/app/code/Magento/Braintree/etc/frontend/events.xml +++ b/app/code/Magento/Braintree/etc/frontend/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/frontend/routes.xml b/app/code/Magento/Braintree/etc/frontend/routes.xml index 7ec5a0c1b097c..ad8b484ca30d6 100644 --- a/app/code/Magento/Braintree/etc/frontend/routes.xml +++ b/app/code/Magento/Braintree/etc/frontend/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/frontend/sections.xml b/app/code/Magento/Braintree/etc/frontend/sections.xml index f87695a624730..add86f4cdb5cc 100644 --- a/app/code/Magento/Braintree/etc/frontend/sections.xml +++ b/app/code/Magento/Braintree/etc/frontend/sections.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/etc/module.xml b/app/code/Magento/Braintree/etc/module.xml index 2b7759fc7843b..b7d0957c2ef6d 100644 --- a/app/code/Magento/Braintree/etc/module.xml +++ b/app/code/Magento/Braintree/etc/module.xml @@ -1,12 +1,12 @@ - + diff --git a/app/code/Magento/Braintree/registration.php b/app/code/Magento/Braintree/registration.php index 33f9f68a4197b..d56ac32c07c96 100644 --- a/app/code/Magento/Braintree/registration.php +++ b/app/code/Magento/Braintree/registration.php @@ -1,6 +1,6 @@ @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Braintree/view/adminhtml/layout/braintree_report_index.xml b/app/code/Magento/Braintree/view/adminhtml/layout/braintree_report_index.xml index 30c334cd09464..396f86b903fd5 100644 --- a/app/code/Magento/Braintree/view/adminhtml/layout/braintree_report_index.xml +++ b/app/code/Magento/Braintree/view/adminhtml/layout/braintree_report_index.xml @@ -1,7 +1,7 @@ @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml index 571c5ededeb99..b5ce1fc9c6792 100644 --- a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml +++ b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml @@ -1,7 +1,7 @@ @@ -18,6 +18,10 @@ braintree_cc_vault Magento_Vault::form/vault.phtml + + braintree_paypal_vault + Magento_Vault::form/vault.phtml + @@ -18,6 +18,10 @@ braintree_cc_vault Magento_Vault::form/vault.phtml + + braintree_paypal_vault + Magento_Vault::form/vault.phtml + - \ No newline at end of file + diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml index cd2fbcf3fec7a..50076da1a851f 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml @@ -1,6 +1,6 @@ getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); +$icon = $details['icon']; +$id = $block->escapeHtml($block->getData('id')); +?> +
    ", + "nonceUrl": "escapeUrl($block->getData('nonceUrl')); ?>" + } + }' id="payment_" class="admin__field"> +
    + + + escapeHtml($details['payerEmail']); ?> +
    +
    diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml index 3811461884725..5414be0a571e9 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml @@ -1,13 +1,13 @@ getData('details'); +$details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); $icon = $block->getData('icons')[$details['type']]; $id = $block->escapeHtml($block->getData('id')); ?> @@ -15,6 +15,7 @@ $id = $block->escapeHtml($block->getData('id')); "Magento_Braintree/js/vault": { "container": "payment_", "publicHash": "escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>", + "code": "escapeHtml($block->getData('code')); ?>", "nonceUrl": "escapeUrl($block->getData('nonceUrl')); ?>" } }' id="payment_" class="admin__field"> diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/grid/tooltip.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/grid/tooltip.phtml index 7a07fe648f315..34c109e2d69f8 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/grid/tooltip.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/grid/tooltip.phtml @@ -1,6 +1,6 @@ escapeHtml($block->getCode()); payment = new Braintree(config); }); //]]> - \ No newline at end of file + diff --git a/app/code/Magento/Braintree/view/adminhtml/ui_component/braintree_report.xml b/app/code/Magento/Braintree/view/adminhtml/ui_component/braintree_report.xml index 031ddca7a8707..7613bbcfd4081 100644 --- a/app/code/Magento/Braintree/view/adminhtml/ui_component/braintree_report.xml +++ b/app/code/Magento/Braintree/view/adminhtml/ui_component/braintree_report.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js index 93b2f9831ef35..2457e3582a15e 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*browser:true*/ diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/grid/filters/status.html b/app/code/Magento/Braintree/view/adminhtml/web/js/grid/filters/status.html index 68c1dbcee8f45..f69cbcc619846 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/grid/filters/status.html +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/grid/filters/status.html @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/grid/provider.js b/app/code/Magento/Braintree/view/adminhtml/web/js/grid/provider.js index ff0f8408b4079..8b1380f6029be 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/grid/provider.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/grid/provider.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js b/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js index fcff173e7fcd4..541542c83bc8f 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*browser:true*/ @@ -14,7 +14,8 @@ define([ return Class.extend({ defaults: { $selector: null, - selector: 'edit_form' + selector: 'edit_form', + $container: null }, /** @@ -25,17 +26,18 @@ define([ var self = this; self.$selector = $('#' + self.selector); + self.$container = $('#' + self.container); self.$selector.on( - 'setVaultNotActive', + 'setVaultNotActive.' + self.getCode(), function () { - self.$selector.off('submitOrder.braintree_vault'); + self.$selector.off('submitOrder.' + self.getCode()); } ); - this._super(); + self._super(); - this.initEventHandlers(); + self.initEventHandlers(); - return this; + return self; }, /** @@ -43,14 +45,14 @@ define([ * @returns {String} */ getCode: function () { - return 'braintree'; + return this.code; }, /** * Init event handlers */ initEventHandlers: function () { - $('#' + this.container).find('[name="payment[token_switcher]"]') + $(this.$container).find('[name="payment[token_switcher]"]') .on('click', this.selectPaymentMethod.bind(this)); }, @@ -66,7 +68,7 @@ define([ * Enable form event listeners */ enableEventListeners: function () { - this.$selector.on('submitOrder.braintree_vault', this.submitOrder.bind(this)); + this.$selector.on('submitOrder.' + this.getCode(), this.submitOrder.bind(this)); }, /** @@ -129,7 +131,7 @@ define([ this.createPublicHashSelector(); this.$selector.find('[name="payment[public_hash]"]').val(this.publicHash); - this.$selector.find('#braintree_nonce').val(nonce); + this.$container.find('#' + this.getNonceSelectorName()).val(nonce); }, /** @@ -138,16 +140,16 @@ define([ createPublicHashSelector: function () { var $input; - if (this.$selector.find('#braintree_nonce').size() === 0) { + if (this.$container.find('#' + this.getNonceSelectorName()).size() === 0) { $input = $('').attr( { type: 'hidden', - id: 'braintree_nonce', + id: this.getNonceSelectorName(), name: 'payment[payment_method_nonce]' } ); - $input.appendTo(this.$selector); + $input.appendTo(this.$container); $input.prop('disabled', false); } }, @@ -160,6 +162,14 @@ define([ alert({ content: message }); + }, + + /** + * Get selector name for nonce input + * @returns {String} + */ + getNonceSelectorName: function () { + return 'nonce_' + this.getCode(); } }); }); diff --git a/app/code/Magento/Braintree/view/adminhtml/web/styles.css b/app/code/Magento/Braintree/view/adminhtml/web/styles.css index 81378f636eb61..19a4f794fb428 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/styles.css +++ b/app/code/Magento/Braintree/view/adminhtml/web/styles.css @@ -1,8 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ .braintree-section .heading {background: url("images/braintree_logo.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;} .braintree-section .button-container {float: right;} -.braintree-section .config-alt {background: url("images/braintree_allinone.png") no-repeat scroll 0 0 / 100% auto; height: 28px; margin: 0.5rem 0 0; width: 230px;} \ No newline at end of file +.braintree-section .config-alt {background: url("images/braintree_allinone.png") no-repeat scroll 0 0 / 100% auto; height: 28px; margin: 0.5rem 0 0; width: 230px;} diff --git a/app/code/Magento/Braintree/view/base/web/js/validator.js b/app/code/Magento/Braintree/view/base/web/js/validator.js index 8c878840ca10c..931774aaffa2d 100644 --- a/app/code/Magento/Braintree/view/base/web/js/validator.js +++ b/app/code/Magento/Braintree/view/base/web/js/validator.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*browser:true*/ diff --git a/app/code/Magento/Braintree/view/frontend/layout/braintree_paypal_review.xml b/app/code/Magento/Braintree/view/frontend/layout/braintree_paypal_review.xml index 19b7a795c2dc2..a5125861a048f 100644 --- a/app/code/Magento/Braintree/view/frontend/layout/braintree_paypal_review.xml +++ b/app/code/Magento/Braintree/view/frontend/layout/braintree_paypal_review.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml index a6b5b8795c46b..872cb7d51ce1f 100644 --- a/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/view/frontend/layout/vault_cards_listaction.xml b/app/code/Magento/Braintree/view/frontend/layout/vault_cards_listaction.xml index c8cd4e3cc4085..1ab68abf1976d 100644 --- a/app/code/Magento/Braintree/view/frontend/layout/vault_cards_listaction.xml +++ b/app/code/Magento/Braintree/view/frontend/layout/vault_cards_listaction.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Braintree/view/frontend/requirejs-config.js b/app/code/Magento/Braintree/view/frontend/requirejs-config.js index 76391a81fe663..e12a4dd87043e 100644 --- a/app/code/Magento/Braintree/view/frontend/requirejs-config.js +++ b/app/code/Magento/Braintree/view/frontend/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml index 35cb4617ec9ed..5fc4bf83cab28 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml @@ -1,6 +1,6 @@ @@ -109,8 +109,8 @@ -
    diff --git a/app/code/Magento/Bundle/etc/extension_attributes.xml b/app/code/Magento/Bundle/etc/extension_attributes.xml index 3819e892e6ff1..d23dfc71b39fc 100644 --- a/app/code/Magento/Bundle/etc/extension_attributes.xml +++ b/app/code/Magento/Bundle/etc/extension_attributes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/frontend/di.xml b/app/code/Magento/Bundle/etc/frontend/di.xml index acaf67ac82e5e..084f681df7e03 100644 --- a/app/code/Magento/Bundle/etc/frontend/di.xml +++ b/app/code/Magento/Bundle/etc/frontend/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/frontend/events.xml b/app/code/Magento/Bundle/etc/frontend/events.xml index ce21acda965de..d08cdb4bcc997 100644 --- a/app/code/Magento/Bundle/etc/frontend/events.xml +++ b/app/code/Magento/Bundle/etc/frontend/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/module.xml b/app/code/Magento/Bundle/etc/module.xml index 982a33d00bc6b..878af07761b42 100644 --- a/app/code/Magento/Bundle/etc/module.xml +++ b/app/code/Magento/Bundle/etc/module.xml @@ -1,12 +1,12 @@ - + diff --git a/app/code/Magento/Bundle/etc/pdf.xml b/app/code/Magento/Bundle/etc/pdf.xml index 085e7946cb7d6..912aa1426efa1 100644 --- a/app/code/Magento/Bundle/etc/pdf.xml +++ b/app/code/Magento/Bundle/etc/pdf.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/product_types.xml b/app/code/Magento/Bundle/etc/product_types.xml index c3f909afbeabe..6168189a3b4e3 100644 --- a/app/code/Magento/Bundle/etc/product_types.xml +++ b/app/code/Magento/Bundle/etc/product_types.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/sales.xml b/app/code/Magento/Bundle/etc/sales.xml index 74e5647051dec..3094eb6bfecfa 100644 --- a/app/code/Magento/Bundle/etc/sales.xml +++ b/app/code/Magento/Bundle/etc/sales.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/webapi.xml b/app/code/Magento/Bundle/etc/webapi.xml index 6e31986be7b8f..69124309687a3 100644 --- a/app/code/Magento/Bundle/etc/webapi.xml +++ b/app/code/Magento/Bundle/etc/webapi.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/webapi_rest/di.xml b/app/code/Magento/Bundle/etc/webapi_rest/di.xml index acaf67ac82e5e..084f681df7e03 100644 --- a/app/code/Magento/Bundle/etc/webapi_rest/di.xml +++ b/app/code/Magento/Bundle/etc/webapi_rest/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/etc/webapi_soap/di.xml b/app/code/Magento/Bundle/etc/webapi_soap/di.xml index acaf67ac82e5e..084f681df7e03 100644 --- a/app/code/Magento/Bundle/etc/webapi_soap/di.xml +++ b/app/code/Magento/Bundle/etc/webapi_soap/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/registration.php b/app/code/Magento/Bundle/registration.php index 48ae2414286eb..53f3657ae0519 100644 --- a/app/code/Magento/Bundle/registration.php +++ b/app/code/Magento/Bundle/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/adminhtml_order_shipment_view.xml b/app/code/Magento/Bundle/view/adminhtml/layout/adminhtml_order_shipment_view.xml index 34975692a948b..0091b151426fa 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/adminhtml_order_shipment_view.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/adminhtml_order_shipment_view.xml @@ -1,14 +1,14 @@ - + diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_bundle.xml b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_bundle.xml index 14ab43a776bbb..d0fdb184355d7 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_bundle.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_bundle.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_new.xml index 251a2ddd68af6..a496ea7538923 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_new.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_view_type_bundle.xml b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_view_type_bundle.xml index 370ef3cf8afca..15374cb987d36 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_view_type_bundle.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/catalog_product_view_type_bundle.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/customer_index_wishlist.xml b/app/code/Magento/Bundle/view/adminhtml/layout/customer_index_wishlist.xml index b7a0c9d03e16e..7e37070109921 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/customer_index_wishlist.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/customer_index_wishlist.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_new.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_new.xml index f3962e20d4435..99fdab5a7e9f4 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_new.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_new.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml index f3962e20d4435..99fdab5a7e9f4 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_view.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_view.xml index 5f2c852416e04..323aba1d186e6 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_view.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_creditmemo_view.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_new.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_new.xml index 9b37d8286fdcb..b03ce7a9cb451 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_new.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_new.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_updateqty.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_updateqty.xml index 9b37d8286fdcb..b03ce7a9cb451 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_updateqty.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_updateqty.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_view.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_view.xml index 752031796631f..34c3470cf06a7 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_view.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_invoice_view.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_view.xml b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_view.xml index 2c6a8fe93caa9..62f0305194fa9 100644 --- a/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_view.xml +++ b/app/code/Magento/Bundle/view/adminhtml/layout/sales_order_view.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml index 0dff5ef370825..84e87afd48180 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml @@ -1,6 +1,6 @@ -decorateArray($block->getOptions()); ?> +decorateArray($block->getOptions(true)); ?>
    diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml index f74f4e3435c67..970b1c38642af 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml index 8fddd0798f7b4..41923c4826432 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css b/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css index 3503d6050e777..d5aed8a7d652e 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css +++ b/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js index a4e47177bdf22..d0c143bde14bd 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js @@ -1,23 +1,24 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -/*jshint browser:true jquery:true*/ + /*global FORM_KEY*/ /*global bSelection*/ /*global $H*/ define([ - "jquery", - "Magento_Catalog/js/product/weight-handler", - "Magento_Ui/js/modal/modal", - "jquery/ui", - "mage/translate", - "Magento_Theme/js/sortable", - "prototype" -], function($, weightHandler, modal){ + 'jquery', + 'Magento_Catalog/js/product/weight-handler', + 'Magento_Ui/js/modal/modal', + 'jquery/ui', + 'mage/translate', + 'Magento_Theme/js/sortable', + 'prototype' +], function ($, weightHandler) { 'use strict'; $.widget('mage.bundleProduct', { + /** @inheritdoc */ _create: function () { this._initOptionBoxes() ._initSortableSelections() @@ -26,7 +27,14 @@ define([ ._bindAddSelectionDialog() ._hideProductTypeSwitcher(); }, + + /** + * @return {Object} + * @private + */ _initOptionBoxes: function () { + var syncOptionTitle; + this.element.sortable({ axis: 'y', handle: '[data-role=draggable-handle]', @@ -35,7 +43,10 @@ define([ tolerance: 'pointer' }); - var syncOptionTitle = function (event) { + /** + * @param {jQuery.Event} event + */ + syncOptionTitle = function (event) { var originalValue = $(event.target).attr('data-original-value'), currentValue = $(event.target).val(), optionBoxTitle = $('.title > span', $(event.target).closest('.option-box')), @@ -48,17 +59,29 @@ define([ 'keyup .field-option-title input[name$="[title]"]': syncOptionTitle, 'paste .field-option-title input[name$="[title]"]': syncOptionTitle }); - + return this; }, + + /** + * @return {Object} + * @private + */ _initSortableSelections: function () { this.element.find('.option-box .form-list tbody').sortable({ axis: 'y', handle: '[data-role=draggable-handle]', - helper: function(event, ui) { - ui.children().each(function() { + + /** + * @param {jQuery.Event} event + * @param {jQuery} ui + * @return {jQuery} + */ + helper: function (event, ui) { + ui.children().each(function () { $(this).width($(this).width()); }); + return ui; }, update: this._updateSelectionsPositions, @@ -67,121 +90,172 @@ define([ return this; }, - _initCheckboxState: function(){ + + /** + * @return {Object} + * @private + */ + _initCheckboxState: function () { this.element.find('.is-required').each(function () { $(this).prop('checked', $(this).closest('.option-box').find('[name$="[required]"]').val() > 0); }); - + this.element.find('.is-user-defined-qty').each(function () { $(this).prop('checked', $(this).closest('.qty-box').find('.select').val() > 0); }); return this; }, + + /** + * @return {Object} + * @private + */ _bindAddSelectionDialog: function () { var widget = this; - this._on({'click .add-selection': function (event) { - var $optionBox = $(event.target).closest('.option-box'), - $selectionGrid = $optionBox.find('.selection-search').clone(), - optionIndex = $optionBox.attr('id').replace('bundle_option_', ''), - productIds = [], - productSkus = [], - selectedProductList = {}; - - $optionBox.find('[name$="[product_id]"]').each(function () { - if (!$(this).closest('tr').find('[name$="[delete]"]').val()) { - productIds.push($(this).val()); - productSkus.push($(this).closest('tr').find('.col-sku').text()); - } - }); - - bSelection.gridSelection.set(optionIndex, $H({})); - bSelection.gridRemoval = $H({}); - bSelection.gridSelectedProductSkus = productSkus; - - $selectionGrid.on('contentUpdated', bSelection.gridUpdateCallback); - $selectionGrid.on('change', '.col-id input', function () {//_on can't be used because of grid reloading - var tr = $(this).closest('tr'); - if ($(this).is(':checked')) { - selectedProductList[$(this).val()] = { - name: $.trim(tr.find('.col-name').html()), - sku: $.trim(tr.find('.col-sku').html()), - product_id: $(this).val(), - option_id: $('bundle_selection_id_' + optionIndex).val(), - selection_price_value: 0, - selection_qty: 1 - }; - } else { - delete selectedProductList[$(this).val()]; - } - }); - - $selectionGrid.modal({ - title: $optionBox.find('input[name$="[title]"]').val() === '' ? - $.mage.__('Add Products to New Option') : - $.mage.__('Add Products to Option "%1"') - .replace('%1',($('
    ').text($optionBox.find('input[name$="[title]"]').val()).html())), - modalClass: 'bundle', - type: 'slide', - closed: function(e, modal) { - modal.modal.remove(); - }, - buttons: [{ - text: $.mage.__('Add Selected Products'), - 'class': 'action-primary action-add', - click: function () { - $.each(selectedProductList, function() { - window.bSelection.addRow(optionIndex, this); - }); - bSelection.gridRemoval.each( - function(pair) { + + this._on({ + /** + * @param {jQuery.Event} event + */ + 'click .add-selection': function (event) { + var $optionBox = $(event.target).closest('.option-box'), + $selectionGrid = $optionBox.find('.selection-search').clone(), + optionIndex = $optionBox.attr('id').replace('bundle_option_', ''), + productIds = [], + productSkus = [], + selectedProductList = {}; + + $optionBox.find('[name$="[product_id]"]').each(function () { + if (!$(this).closest('tr').find('[name$="[delete]"]').val()) { + productIds.push($(this).val()); + productSkus.push($(this).closest('tr').find('.col-sku').text()); + } + }); + + bSelection.gridSelection.set(optionIndex, $H({})); + bSelection.gridRemoval = $H({}); + bSelection.gridSelectedProductSkus = productSkus; + + $selectionGrid.on('contentUpdated', bSelection.gridUpdateCallback); + $selectionGrid.on('change', '.col-id input', function () { + var tr = $(this).closest('tr'); + + if ($(this).is(':checked')) { + selectedProductList[$(this).val()] = { + name: $.trim(tr.find('.col-name').html()), + sku: $.trim(tr.find('.col-sku').html()), + 'product_id': $(this).val(), + 'option_id': $('bundle_selection_id_' + optionIndex).val(), + 'selection_price_value': 0, + 'selection_qty': 1 + }; + } else { + delete selectedProductList[$(this).val()]; + } + }); + + $selectionGrid.modal({ + title: $optionBox.find('input[name$="[title]"]').val() === '' ? + $.mage.__('Add Products to New Option') : + $.mage.__('Add Products to Option "%1"').replace( + '%1', + $('
    ').text($optionBox.find('input[name$="[title]"]').val()).html() + ), + modalClass: 'bundle', + type: 'slide', + + /** + * @param {jQuery.Event} e + * @param {Object} modalWindow + */ + closed: function (e, modalWindow) { + modalWindow.modal.remove(); + }, + buttons: [{ + text: $.mage.__('Add Selected Products'), + 'class': 'action-primary action-add', + + /** Click action. */ + click: function () { + $.each(selectedProductList, function () { + window.bSelection.addRow(optionIndex, this); + }); + bSelection.gridRemoval.each(function (pair) { $optionBox.find('.col-sku').filter(function () { return $.trim($(this).text()) === pair.key; // find row by SKU }).closest('tr').find('button.delete').trigger('click'); - } - ); - widget.refreshSortableElements(); - widget._updateSelectionsPositions.apply(widget.element); - $selectionGrid.modal('closeModal'); - } - }] - }); - $.ajax({ - url: bSelection.selectionSearchUrl, - dataType: 'html', - data: { - index: optionIndex, - products: productIds, - selected_products: productIds, - form_key: FORM_KEY - }, - success: function(data) { - $selectionGrid.html(data).modal('openModal'); - }, - context: $('body'), - showLoader: true - }); - }}); + }); + widget.refreshSortableElements(); + widget._updateSelectionsPositions.apply(widget.element); + $selectionGrid.modal('closeModal'); + } + }] + }); + $.ajax({ + url: bSelection.selectionSearchUrl, + dataType: 'html', + data: { + index: optionIndex, + products: productIds, + 'selected_products': productIds, + 'form_key': FORM_KEY + }, + + /** + * @param {*} data + */ + success: function (data) { + $selectionGrid.html(data).modal('openModal'); + }, + context: $('body'), + showLoader: true + }); + } + }); return this; }, + + /** + * @private + */ _hideProductTypeSwitcher: function () { weightHandler.hideWeightSwitcher(); }, + + /** + * @return {Object} + * @private + */ _bindCheckboxHandlers: function () { this._on({ + /** + * @param {jQuery.Event} event + */ 'change .is-required': function (event) { var $this = $(event.target); + $this.closest('.option-box').find('[name$="[required]"]').val($this.is(':checked') ? 1 : 0); }, + + /** + * @param {jQuery.Event} event + */ 'change .is-user-defined-qty': function (event) { var $this = $(event.target); + $this.closest('.qty-box').find('.select').val($this.is(':checked') ? 1 : 0); } }); return this; }, + + /** + * @return {Object} + * @private + */ _updateOptionBoxPositions: function () { $(this).find('[name^=bundle_options][name$="[position]"]').each(function (index) { $(this).val(index); @@ -189,6 +263,11 @@ define([ return this; }, + + /** + * @return {Object} + * @private + */ _updateSelectionsPositions: function () { $(this).find('[name^=bundle_selections][name$="[position]"]').each(function (index) { $(this).val(index); @@ -196,6 +275,11 @@ define([ return this; }, + + /** + * + * @return {Object} + */ refreshSortableElements: function () { this.element.sortable('refresh'); this._updateOptionBoxPositions.apply(this.element); diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-type-handler.js b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-type-handler.js index 12dda7d3a148c..7f4a57cb530da 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-type-handler.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-type-handler.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*jshint browser:true jquery:true expr:true*/ diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js index b7a05076ae268..9c2432bade43f 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-checkbox.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js new file mode 100644 index 0000000000000..6265394cf38e3 --- /dev/null +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js @@ -0,0 +1,59 @@ +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid' +], function (_, dynamicRowsGrid) { + 'use strict'; + + return dynamicRowsGrid.extend({ + defaults: { + label: '', + columnsHeader: false, + columnsHeaderAfterRender: true, + addButton: false + }, + + /** + * Initialize elements from grid + * + * @param {Array} data + * + * @returns {Object} Chainable. + */ + initElements: function (data) { + var newData = this.getNewData(data), + recordIndex; + + this.parsePagesData(data); + + if (newData.length) { + if (this.insertData().length) { + recordIndex = data.length - newData.length - 1; + + _.each(newData, function (newRecord) { + this.processingAddChild(newRecord, ++recordIndex, newRecord[this.identificationProperty]); + }, this); + } + } + + return this; + }, + + /** + * Mapping value from grid + * + * @param {Array} data + */ + mappingValue: function (data) { + if (_.isEmpty(data)) { + return; + } + + this._super(); + } + }); +}); diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js new file mode 100644 index 0000000000000..4bbd1b3e4da20 --- /dev/null +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js @@ -0,0 +1,98 @@ +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'mageUtils', + 'uiRegistry', + 'Magento_Ui/js/dynamic-rows/dynamic-rows' +], function (_, utils, registry, dynamicRows) { + 'use strict'; + + return dynamicRows.extend({ + defaults: { + label: '', + collapsibleHeader: true, + columnsHeader: false, + deleteProperty: false, + addButton: false + }, + + /** + * Set new data to dataSource, + * delete element + * + * @param {Array} data - record data + */ + _updateData: function (data) { + var elems = _.clone(this.elems()), + path, + dataArr, + optionBaseData; + + dataArr = this.recordData.splice(this.startIndex, this.recordData().length - this.startIndex); + dataArr.splice(0, this.pageSize); + elems = _.sortBy(this.elems(), function (elem) { + return ~~elem.index; + }); + + data.concat(dataArr).forEach(function (rec, idx) { + if (elems[idx]) { + elems[idx].recordId = rec[this.identificationProperty]; + } + + if (!rec.position) { + rec.position = this.maxPosition; + this.setMaxPosition(); + } + + path = this.dataScope + '.' + this.index + '.' + (this.startIndex + idx); + optionBaseData = _.pick(rec, function (value) { + return !_.isObject(value); + }); + this.source.set(path, optionBaseData); + this.source.set(path + '.bundle_button_proxy', []); + this.source.set(path + '.bundle_selections', []); + this.removeBundleItemsFromOption(idx); + _.each(rec['bundle_selections'], function (obj, index) { + this.source.set(path + '.bundle_button_proxy' + '.' + index, rec['bundle_button_proxy'][index]); + this.source.set(path + '.bundle_selections' + '.' + index, obj); + }, this); + }, this); + + this.elems(elems); + }, + + /** + * Removes nested dynamic-rows-grid rendered records from option + * + * @param {Number|String} index - element index + */ + removeBundleItemsFromOption: function (index) { + var bundleSelections = registry.get(this.name + '.' + index + '.' + this.bundleSelectionsName), + bundleSelectionsLength = (bundleSelections.elems() || []).length, + i; + + if (bundleSelectionsLength) { + for (i = 0; i < bundleSelectionsLength; i++) { + bundleSelections.elems()[0].destroy(); + } + } + }, + + /** + * {@inheritdoc} + */ + processingAddChild: function (ctx, index, prop) { + var recordIds = _.map(this.recordData(), function (rec) { + return parseInt(rec['record_id'], 10); + }), + maxRecordId = _.max(recordIds); + + prop = maxRecordId > -1 ? maxRecordId + 1 : prop; + this._super(ctx, index, prop); + } + }); +}); diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js index 14dd426ed02aa..cee0489cc901a 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-input-type.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js index b5c1d61d41f20..d5be0a19b597d 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Bundle/view/base/layout/catalog_product_prices.xml b/app/code/Magento/Bundle/view/base/layout/catalog_product_prices.xml index 92d76cdfaa580..5f7d22ba2650b 100644 --- a/app/code/Magento/Bundle/view/base/layout/catalog_product_prices.xml +++ b/app/code/Magento/Bundle/view/base/layout/catalog_product_prices.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml b/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml index 4c11b0ba1fe50..efd75677d3061 100644 --- a/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml @@ -1,6 +1,6 @@ getTierPriceList(); ' . $tierPriceModel->getSavePercent($price['price']) . '%' + '' . round($price['percentage_value']) . '%' ); ?>
  • diff --git a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js index 1a76acf174756..c6dcc9838f062 100644 --- a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js +++ b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js @@ -1,7 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + define([ 'jquery', 'underscore', @@ -48,7 +49,10 @@ define([ priceBox = $(this.options.priceBoxSelector, form), qty = $(this.options.qtyFieldSelector, form); - if (priceBox.data('magePriceBox') && priceBox.priceBox('option') && priceBox.priceBox('option').priceConfig) { + if (priceBox.data('magePriceBox') && + priceBox.priceBox('option') && + priceBox.priceBox('option').priceConfig + ) { if (priceBox.priceBox('option').priceConfig.optionTemplate) { this._setOption('optionTemplate', priceBox.priceBox('option').priceConfig.optionTemplate); } @@ -78,7 +82,7 @@ define([ if (handler && handler instanceof Function) { changes = handler(bundleOption, this.options.optionConfig, this); } else { - changes = defaultGetOptionValue(bundleOption, this.options.optionConfig); + changes = defaultGetOptionValue(bundleOption, this.options.optionConfig);//eslint-disable-line } if (changes) { @@ -117,12 +121,13 @@ define([ */ _applyQtyFix: function applyQtyFix() { var config = this.options.optionConfig; + if (config.isFixedPrice) { _.each(config.options, function (option) { _.each(option.selections, function (item) { if (item.qty && item.qty !== 1) { _.each(item.prices, function (price) { - price.amount = price.amount / item.qty; + price.amount /= item.qty; }); } }); @@ -141,13 +146,13 @@ define([ var config = this.options, format = config.priceFormat, template = config.optionTemplate; + template = mageTemplate(template); options.filter('select').each(function (index, element) { var $element = $(element), optionId = utils.findOptionId($element), - optionName = $element.prop('name'), - optionType = $element.prop('type'), - optionConfig = config.optionConfig && config.optionConfig.options[optionId].selections; + optionConfig = config.optionConfig && config.optionConfig.options[optionId].selections, + value; $element.find('option').each(function (idx, option) { var $option, @@ -170,8 +175,8 @@ define([ prices = optionConfig[optionValue].prices; _.each(prices, function (price, type) { - var value = +(price.amount); - value += _.reduce(price.adjustments, function (sum, x) { + value = +price.amount; + value += _.reduce(price.adjustments, function (sum, x) {//eslint-disable-line return sum + x; }, 0); toTemplate.data[type] = { @@ -247,13 +252,17 @@ define([ if (optionValue) { optionQty = optionConfig[optionValue].qty || 0; canQtyCustomize = optionConfig[optionValue].customQty === '1'; - toggleQtyField(qtyField, optionQty, optionId, optionValue, canQtyCustomize); + toggleQtyField(qtyField, optionQty, optionId, optionValue, canQtyCustomize);//eslint-disable-line tempChanges = utils.deepClone(optionConfig[optionValue].prices); - tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig[optionValue]); - tempChanges = applyQty(tempChanges, optionQty); + tempChanges = applyTierPrice(//eslint-disable-line + tempChanges, + optionQty, + optionConfig[optionValue] + ); + tempChanges = applyQty(tempChanges, optionQty);//eslint-disable-line } else { tempChanges = {}; - toggleQtyField(qtyField, '0', optionId, optionValue, false); + toggleQtyField(qtyField, '0', optionId, optionValue, false);//eslint-disable-line } optionHash = 'bundle-option-' + optionName; changes[optionHash] = tempChanges; @@ -267,8 +276,8 @@ define([ optionHash = 'bundle-option-' + optionName + '##' + optionValueCode; optionQty = row.qty || 0; tempChanges = utils.deepClone(row.prices); - tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig); - tempChanges = applyQty(tempChanges, optionQty); + tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig);//eslint-disable-line + tempChanges = applyQty(tempChanges, optionQty);//eslint-disable-line changes[optionHash] = _.contains(optionValue, optionValueCode) ? tempChanges : {}; }); @@ -279,8 +288,8 @@ define([ optionHash = 'bundle-option-' + optionName + '##' + optionValue; optionQty = optionConfig[optionValue].qty || 0; tempChanges = utils.deepClone(optionConfig[optionValue].prices); - tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig); - tempChanges = applyQty(tempChanges, optionQty); + tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig);//eslint-disable-line + tempChanges = applyQty(tempChanges, optionQty);//eslint-disable-line changes[optionHash] = element.is(':checked') ? tempChanges : {}; selectedIds[optionId] = selectedIds[optionId] || []; @@ -295,9 +304,13 @@ define([ case 'hidden': optionHash = 'bundle-option-' + optionName + '##' + optionValue; optionQty = optionConfig[optionValue].qty || 0; + canQtyCustomize = optionConfig[optionValue].customQty === '1'; + qtyField = element.data('qtyField'); + qtyField.data('option', element); + toggleQtyField(qtyField, optionQty, optionId, optionValue, canQtyCustomize);//eslint-disable-line tempChanges = utils.deepClone(optionConfig[optionValue].prices); - tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig); - tempChanges = applyQty(tempChanges, optionQty); + tempChanges = applyTierPrice(tempChanges, optionQty, optionConfig);//eslint-disable-line + tempChanges = applyQty(tempChanges, optionQty);//eslint-disable-line optionHash = 'bundle-option-' + optionName; changes[optionHash] = tempChanges; @@ -362,11 +375,9 @@ define([ lowest = false; _.each(tiers, function (tier, index) { - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - if (tier.price_qty > qty) { + if (tier['price_qty'] > qty) { return; } - // jscs:enable requireCamelCaseOrUpperCaseIdentifiers if (tier.prices[magicKey].amount < oneItemPrice[magicKey].amount) { lowest = index; diff --git a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml index 5ab962f0b91b5..2c66156cbc7a1 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_simple.xml b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_simple.xml index e31437ec63963..fedd29f952b02 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_simple.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_simple.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_configure_type_bundle.xml b/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_configure_type_bundle.xml index e7128e45ddc7b..adb1b2911983b 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_configure_type_bundle.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_configure_type_bundle.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_item_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_item_renderers.xml index c5d613183e640..9abf0a4018980 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_item_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/checkout_cart_item_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/checkout_onepage_review_item_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/checkout_onepage_review_item_renderers.xml index 09a27341fd5da..ddede2340995b 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/checkout_onepage_review_item_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/checkout_onepage_review_item_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/default.xml b/app/code/Magento/Bundle/view/frontend/layout/default.xml index a54d4b652b685..cb14616af5980 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/default.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/default.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_creditmemo_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_creditmemo_renderers.xml index 4db5f73f55bc5..991011db9fa08 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_creditmemo_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_creditmemo_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_invoice_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_invoice_renderers.xml index 1dba5769c0207..0e32c9fd9e816 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_invoice_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_invoice_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_renderers.xml index 6e1b90abf4d4e..927214fbcc174 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_shipment_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_shipment_renderers.xml index 7846245018a74..7463caa738083 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_shipment_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_email_order_shipment_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_creditmemo_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_creditmemo_renderers.xml index 7896e92bd34f4..c94b4957d267b 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_creditmemo_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_creditmemo_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_invoice_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_invoice_renderers.xml index 850e546a93a5a..d07959385bd9f 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_invoice_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_invoice_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_item_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_item_renderers.xml index 317b142514b9d..fb26de5bc2fdd 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_item_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_item_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_creditmemo_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_creditmemo_renderers.xml index dc5fb238dfd77..8c328c87a5c65 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_creditmemo_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_creditmemo_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_invoice_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_invoice_renderers.xml index 450f18d863ab5..57e39795df7da 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_invoice_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_invoice_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_renderers.xml index c4741ad6119f5..510168ac55e8a 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_shipment_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_shipment_renderers.xml index e3c91f1da5c20..f46b6260f3bd8 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_shipment_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_print_shipment_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/layout/sales_order_shipment_renderers.xml b/app/code/Magento/Bundle/view/frontend/layout/sales_order_shipment_renderers.xml index ce42a5466cf06..536953423a3f2 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/sales_order_shipment_renderers.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/sales_order_shipment_renderers.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Bundle/view/frontend/requirejs-config.js b/app/code/Magento/Bundle/view/frontend/requirejs-config.js index 9dd12524124a6..51ab4cab6bb2c 100644 --- a/app/code/Magento/Bundle/view/frontend/requirejs-config.js +++ b/app/code/Magento/Bundle/view/frontend/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -12,4 +12,4 @@ var config = { productSummary: 'Magento_Bundle/js/product-summary' } } -}; \ No newline at end of file +}; diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml index ddb483cf0220d..86cd52b6dc6fd 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml index e72be96db1ab0..e3538ebd6caea 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml index cb1de4e05e8a4..0fc7e6f9c8f06 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml @@ -1,6 +1,6 @@ fieldset').focus(); }, - _hide: function() { + + /** + * @private + */ + _hide: function () { $('html, body').animate({ scrollTop: 0 }, 600); $(this.options.bundleOptionsContainer).slideUp(800); } }); - + return $.mage.slide; -}); \ No newline at end of file +}); diff --git a/app/code/Magento/BundleImportExport/Model/Export/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Export/Product/Type/Bundle.php index c9b6aa4c45ab3..5219de4b4a2fb 100644 --- a/app/code/Magento/BundleImportExport/Model/Export/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Export/Product/Type/Bundle.php @@ -1,6 +1,6 @@ parseAdditionalAttributes($dataRow['additional_attributes']); $dataRow['additional_attributes'] = $this->getNotBundleAttributes($additionalAttributes); } @@ -349,17 +346,38 @@ protected function cleanNotBundleAdditionalAttributes($dataRow) */ protected function getNotBundleAttributes($additionalAttributes) { - $cleanedAdditionalAttributes = ''; - foreach ($additionalAttributes as $attribute) { - list($attributeCode, $attributeValue) = explode(ImportProductModel::PAIR_NAME_VALUE_SEPARATOR, $attribute); - if (!in_array('bundle_' . $attributeCode, $this->getBundleColumns())) { - $cleanedAdditionalAttributes .= $attributeCode - . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $attributeValue - . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR; + $filteredAttributes = []; + foreach ($additionalAttributes as $code => $value) { + if (!in_array('bundle_' . $code, $this->getBundleColumns())) { + $filteredAttributes[] = $code . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $value; } } + return implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $filteredAttributes); + } - return rtrim($cleanedAdditionalAttributes, ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR); + /** + * Retrieves additional attributes as array code=>value. + * + * @param string $additionalAttributes + * @return array + */ + private function parseAdditionalAttributes($additionalAttributes) + { + $attributeNameValuePairs = explode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes); + $preparedAttributes = []; + $code = ''; + foreach ($attributeNameValuePairs as $attributeData) { + //process case when attribute has ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR inside its value + if (strpos($attributeData, ImportProductModel::PAIR_NAME_VALUE_SEPARATOR) === false) { + if (!$code) { + continue; + } + $preparedAttributes[$code] .= ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR . $attributeData; + continue; + } + list($code, $value) = explode(ImportProductModel::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2); + $preparedAttributes[$code] = $value; + } + return $preparedAttributes; } } diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 2d0f1264b6b8a..d5548f61c2bf2 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -3,13 +3,12 @@ /** * Import entity of bundle product type * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\BundleImportExport\Model\Import\Product\Type; use \Magento\Bundle\Model\Product\Price as BundlePrice; -use \Magento\BundleImportExport\Model\Export\RowCustomizer; use \Magento\Catalog\Model\Product\Type\AbstractType; /** @@ -55,20 +54,6 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst */ const SELECTION_PRICE_TYPE_PERCENT = 1; - /** - * Instance of database adapter. - * - * @var \Magento\Framework\DB\Adapter\AdapterInterface - */ - protected $connection; - - /** - * Instance of application resource. - * - * @var \Magento\Framework\App\ResourceConnection - */ - protected $_resource; - /** * Array of cached options. * @@ -144,23 +129,6 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst 'multiselect' => 'multi', ]; - /** - * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac - * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac - * @param \Magento\Framework\App\ResourceConnection $resource - * @param array $params - */ - public function __construct( - \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac, - \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac, - \Magento\Framework\App\ResourceConnection $resource, - array $params - ) { - parent::__construct($attrSetColFac, $prodAttrColFac, $resource, $params); - $this->_resource = $resource; - $this->connection = $resource->getConnection(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION); - } - /** * Parse selections. * diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php index e7c313c56034b..0e67a2bc737c9 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php @@ -1,6 +1,6 @@ rowCustomizerMock->prepareData($this->productResourceCollection, [1]); - $attributes = 'attribute=1,sku_type=1,price_type=1,price_view=1,weight_type=1,values=values,shipment_type=1'; + $attributes = 'attribute=1,sku_type=1,attribute2="Text",price_type=1,price_view=1,weight_type=1,' + . 'values=values,shipment_type=1,attribute3=One,Two,Three'; $dataRow = [ 'sku' => 'sku1', 'additional_attributes' => $attributes @@ -186,7 +187,7 @@ public function testAddData() $preparedRow = $preparedData->addData($dataRow, 1); $expected = [ 'sku' => 'sku1', - 'additional_attributes' => 'attribute=1', + 'additional_attributes' => 'attribute=1,attribute2="Text",attribute3=One,Two,Three', 'bundle_price_type' => 'fixed', 'bundle_shipment_type' => 'separately', 'bundle_sku_type' => 'fixed', diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php index 2379ad7f10701..fd25dc247c341 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/BundleImportExport/etc/export.xml b/app/code/Magento/BundleImportExport/etc/export.xml index 04312d79f0260..c7fd951bfab53 100644 --- a/app/code/Magento/BundleImportExport/etc/export.xml +++ b/app/code/Magento/BundleImportExport/etc/export.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/BundleImportExport/etc/import.xml b/app/code/Magento/BundleImportExport/etc/import.xml index 2c23489002b1f..8daa5296a8c39 100644 --- a/app/code/Magento/BundleImportExport/etc/import.xml +++ b/app/code/Magento/BundleImportExport/etc/import.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/BundleImportExport/etc/module.xml b/app/code/Magento/BundleImportExport/etc/module.xml index d042b3bc7eb79..64f2c06b3c770 100644 --- a/app/code/Magento/BundleImportExport/etc/module.xml +++ b/app/code/Magento/BundleImportExport/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/BundleImportExport/registration.php b/app/code/Magento/BundleImportExport/registration.php index b417e7d20b79e..b4f80e749f130 100644 --- a/app/code/Magento/BundleImportExport/registration.php +++ b/app/code/Magento/BundleImportExport/registration.php @@ -1,6 +1,6 @@ getEvent()->getObject(); + if (!is_object($object)) { + return; + } if ($this->config->getType() == \Magento\PageCache\Model\Config::VARNISH && $this->config->isEnabled()) { - $object = $observer->getEvent()->getObject(); - if ($object instanceof \Magento\Framework\DataObject\IdentityInterface) { - $tags = []; - $pattern = "((^|,)%s(,|$))"; - foreach ($object->getIdentities() as $tag) { - $tags[] = sprintf($pattern, $tag); - } - if (!empty($tags)) { - $this->purgeCache->sendPurgeRequest(implode('|', array_unique($tags))); - } + $bareTags = $this->getTagResolver()->getTags($object); + + $tags = []; + $pattern = "((^|,)%s(,|$))"; + foreach ($bareTags as $tag) { + $tags[] = sprintf($pattern, $tag); + } + if (!empty($tags)) { + $this->purgeCache->sendPurgeRequest(implode('|', array_unique($tags))); } + + } + } + + /** + * @deprecated + * @return \Magento\Framework\App\Cache\Tag\Resolver + */ + private function getTagResolver() + { + if ($this->tagResolver === null) { + $this->tagResolver = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\App\Cache\Tag\Resolver::class); } + return $this->tagResolver; } } diff --git a/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php b/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php index 3cb6d8a054eb1..d4728fac0a17c 100644 --- a/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php @@ -1,6 +1,6 @@ configMock = $this->getMock( \Magento\PageCache\Model\Config::class, ['getType', 'isEnabled'], @@ -39,6 +46,10 @@ protected function setUp() $this->configMock, $this->purgeCache ); + + $this->tagResolver = $this->getMock(\Magento\Framework\App\Cache\Tag\Resolver::class, [], [], '', false); + $helper->setBackwardCompatibleProperty($this->model, 'tagResolver', $this->tagResolver); + $this->observerMock = $this->getMock( \Magento\Framework\Event\Observer::class, ['getEvent'], @@ -65,10 +76,12 @@ public function testInvalidateVarnish() )->will( $this->returnValue(\Magento\PageCache\Model\Config::VARNISH) ); + $eventMock = $this->getMock(\Magento\Framework\Event::class, ['getObject'], [], '', false); $eventMock->expects($this->once())->method('getObject')->will($this->returnValue($this->observerObject)); $this->observerMock->expects($this->once())->method('getEvent')->will($this->returnValue($eventMock)); - $this->observerObject->expects($this->once())->method('getIdentities')->will($this->returnValue($tags)); + $this->tagResolver->expects($this->once())->method('getTags')->with($this->observerObject) + ->will($this->returnValue($tags)); $this->purgeCache->expects($this->once())->method('sendPurgeRequest')->with($pattern); $this->model->execute($this->observerMock); diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index cafccadb41ad8..c886e069b66e3 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cache-invalidate", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-page-cache": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/CacheInvalidate/etc/events.xml b/app/code/Magento/CacheInvalidate/etc/events.xml index 58ddeb64c9257..6103e661de55c 100644 --- a/app/code/Magento/CacheInvalidate/etc/events.xml +++ b/app/code/Magento/CacheInvalidate/etc/events.xml @@ -1,7 +1,7 @@ @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/CacheInvalidate/etc/module.xml b/app/code/Magento/CacheInvalidate/etc/module.xml index 7cc9d59df5959..b3277477fb62d 100644 --- a/app/code/Magento/CacheInvalidate/etc/module.xml +++ b/app/code/Magento/CacheInvalidate/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/CacheInvalidate/registration.php b/app/code/Magento/CacheInvalidate/registration.php index 21f5baf8f333c..00ddee3f6776b 100644 --- a/app/code/Magento/CacheInvalidate/registration.php +++ b/app/code/Magento/CacheInvalidate/registration.php @@ -1,6 +1,6 @@ serializer = $serializer; + $this->captchaHelper = $captchaHelper; + } + /** * {@inheritdoc} */ public function execute() { $formId = $this->getRequest()->getPost('formId'); - $captchaModel = $this->_objectManager->get(\Magento\Captcha\Helper\Data::class)->getCaptcha($formId); + $captchaModel = $this->captchaHelper->getCaptcha($formId); $this->_view->getLayout()->createBlock( $captchaModel->getBlockName() )->setFormId( @@ -24,7 +50,7 @@ public function execute() )->setIsAjax( true )->toHtml(); - $this->getResponse()->representJson(json_encode(['imgSrc' => $captchaModel->getImgSrc()])); + $this->getResponse()->representJson($this->serializer->serialize(['imgSrc' => $captchaModel->getImgSrc()])); $this->_actionFlag->set('', self::FLAG_NO_POST_DISPATCH, true); } diff --git a/app/code/Magento/Captcha/Controller/Refresh/Index.php b/app/code/Magento/Captcha/Controller/Refresh/Index.php index eac5aef1946c7..ef757d1b2fc35 100644 --- a/app/code/Magento/Captcha/Controller/Refresh/Index.php +++ b/app/code/Magento/Captcha/Controller/Refresh/Index.php @@ -3,7 +3,7 @@ * Refreshes captcha and returns JSON encoded URL to image (AJAX action) * Example: {'imgSrc': 'http://example.com/media/captcha/67842gh187612ngf8s.png'} * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Captcha\Controller\Refresh; @@ -17,13 +17,25 @@ class Index extends \Magento\Framework\App\Action\Action */ protected $captchaHelper; + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + protected $serializer; + /** * @param Context $context * @param \Magento\Captcha\Helper\Data $captchaHelper + * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + * @throws \RuntimeException */ - public function __construct(Context $context, \Magento\Captcha\Helper\Data $captchaHelper) - { + public function __construct( + Context $context, + \Magento\Captcha\Helper\Data $captchaHelper, + \Magento\Framework\Serialize\Serializer\Json $serializer = null + ) { $this->captchaHelper = $captchaHelper; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); parent::__construct($context); } @@ -34,23 +46,19 @@ public function execute() { $formId = $this->_request->getPost('formId'); if (null === $formId) { - try { - $params = []; - $content = $this->_request->getContent(); - if ($content) { - $params = \Zend_Json::decode($content); - } - $formId = isset($params['formId']) ? $params['formId'] : null; - } catch (\Zend_Json_Exception $exception) { - $formId = null; + $params = []; + $content = $this->_request->getContent(); + if ($content) { + $params = $this->serializer->unserialize($content); } + $formId = isset($params['formId']) ? $params['formId'] : null; } $captchaModel = $this->captchaHelper->getCaptcha($formId); $captchaModel->generate(); $block = $this->_view->getLayout()->createBlock($captchaModel->getBlockName()); $block->setFormId($formId)->setIsAjax(true)->toHtml(); - $this->_response->representJson(json_encode(['imgSrc' => $captchaModel->getImgSrc()])); + $this->_response->representJson($this->serializer->serialize(['imgSrc' => $captchaModel->getImgSrc()])); $this->_actionFlag->set('', self::FLAG_NO_POST_DISPATCH, true); } } diff --git a/app/code/Magento/Captcha/Cron/DeleteExpiredImages.php b/app/code/Magento/Captcha/Cron/DeleteExpiredImages.php index 11ebf3438eaa2..af1a6e5687d03 100644 --- a/app/code/Magento/Captcha/Cron/DeleteExpiredImages.php +++ b/app/code/Magento/Captcha/Cron/DeleteExpiredImages.php @@ -1,6 +1,6 @@ helper = $helper; $this->sessionManager = $sessionManager; $this->resultJsonFactory = $resultJsonFactory; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); $this->formIds = $formIds; } @@ -53,7 +63,6 @@ public function __construct( * @param \Magento\Customer\Controller\Ajax\Login $subject * @param \Closure $proceed * @return $this - * @throws \Zend_Json_Exception * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -70,7 +79,7 @@ public function aroundExecute( $loginParams = []; $content = $request->getContent(); if ($content) { - $loginParams = \Zend_Json::decode($content); + $loginParams = $this->serializer->unserialize($content); } $username = isset($loginParams['username']) ? $loginParams['username'] : null; $captchaString = isset($loginParams[$captchaInputName]) ? $loginParams[$captchaInputName] : null; diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php index c5a99de5ba178..a977ad6923611 100644 --- a/app/code/Magento/Captcha/Model/DefaultModel.php +++ b/app/code/Magento/Captcha/Model/DefaultModel.php @@ -1,16 +1,16 @@ */ -class DefaultModel extends \Zend_Captcha_Image implements \Magento\Captcha\Model\CaptchaInterface +class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model\CaptchaInterface { /** * Key in session for captcha code @@ -30,49 +30,50 @@ class DefaultModel extends \Zend_Captcha_Image implements \Magento\Captcha\Model /** * @var \Magento\Captcha\Helper\Data */ - protected $_captchaData; + protected $captchaData; /** * Captcha expire time * @var int */ - protected $_expiration; + protected $expiration; /** * Override default value to prevent a captcha cut off * @var int - * @see \Zend_Captcha_Image::$_fsize + * @see \Zend\Captcha\Image::$fsize */ - protected $_fsize = 22; + protected $fsize = 22; /** * Captcha form id * @var string */ - protected $_formId; + protected $formId; /** * @var \Magento\Captcha\Model\ResourceModel\LogFactory */ - protected $_resLogFactory; + protected $resLogFactory; /** * Overrides parent parameter as session comes in constructor. * * @var bool */ - protected $_keepSession = true; + protected $keepSession = true; /** * @var \Magento\Framework\Session\SessionManagerInterface */ - protected $_session; + protected $session; /** * @param \Magento\Framework\Session\SessionManagerInterface $session * @param \Magento\Captcha\Helper\Data $captchaData - * @param \Magento\Captcha\Model\ResourceModel\LogFactory $resLogFactory + * @param ResourceModel\LogFactory $resLogFactory * @param string $formId + * @throws \Zend\Captcha\Exception\ExtensionNotLoadedException */ public function __construct( \Magento\Framework\Session\SessionManagerInterface $session, @@ -80,10 +81,11 @@ public function __construct( \Magento\Captcha\Model\ResourceModel\LogFactory $resLogFactory, $formId ) { - $this->_session = $session; - $this->_captchaData = $captchaData; - $this->_resLogFactory = $resLogFactory; - $this->_formId = $formId; + parent::__construct(); + $this->session = $session; + $this->captchaData = $captchaData; + $this->resLogFactory = $resLogFactory; + $this->formId = $formId; } /** @@ -92,9 +94,9 @@ public function __construct( * @param string $key * @return string */ - protected function _getFormIdKey($key) + private function getFormIdKey($key) { - return $this->_formId . '_' . $key; + return $this->formId . '_' . $key; } /** @@ -115,19 +117,21 @@ public function getBlockName() */ public function isRequired($login = null) { - if ($this->_isUserAuth() && !$this->isShownToLoggedInUser() || !$this->_isEnabled() || !in_array( - $this->_formId, - $this->_getTargetForms() - ) + if ( + $this->isUserAuth() + && !$this->isShownToLoggedInUser() + || !$this->isEnabled() + || !in_array( + $this->formId, + $this->getTargetForms() + ) ) { return false; } - return $this->_isShowAlways() || $this->_isOverLimitAttempts( - $login - ) || $this->_session->getData( - $this->_getFormIdKey('show_captcha') - ); + return $this->isShowAlways() + || $this->isOverLimitAttempts($login) + || $this->session->getData($this->getFormIdKey('show_captcha')); } /** @@ -137,9 +141,9 @@ public function isRequired($login = null) */ public function isShownToLoggedInUser() { - $forms = (array)$this->_captchaData->getConfig('shown_to_logged_in_user'); + $forms = (array)$this->captchaData->getConfig('shown_to_logged_in_user'); foreach ($forms as $formId => $isShownToLoggedIn) { - if ($isShownToLoggedIn && $this->_formId == $formId) { + if ($isShownToLoggedIn && $this->formId == $formId) { return true; } } @@ -152,9 +156,9 @@ public function isShownToLoggedInUser() * @param string $login * @return bool */ - protected function _isOverLimitAttempts($login) + private function isOverLimitAttempts($login) { - return $this->_isOverLimitIpAttempt() || $this->_isOverLimitLoginAttempts($login); + return $this->isOverLimitIpAttempt() || $this->isOverLimitLoginAttempts($login); } /** @@ -162,9 +166,9 @@ protected function _isOverLimitAttempts($login) * * @return int */ - protected function _getAllowedAttemptsForSameLogin() + private function getAllowedAttemptsForSameLogin() { - return (int)$this->_captchaData->getConfig('failed_attempts_login'); + return (int)$this->captchaData->getConfig('failed_attempts_login'); } /** @@ -172,20 +176,20 @@ protected function _getAllowedAttemptsForSameLogin() * * @return int */ - protected function _getAllowedAttemptsFromSameIp() + private function getAllowedAttemptsFromSameIp() { - return (int)$this->_captchaData->getConfig('failed_attempts_ip'); + return (int)$this->captchaData->getConfig('failed_attempts_ip'); } /** - * Check is overlimit saved attempts from one ip + * Check is over limit saved attempts from one ip * * @return bool */ - protected function _isOverLimitIpAttempt() + private function isOverLimitIpAttempt() { - $countAttemptsByIp = $this->_getResourceModel()->countAttemptsByRemoteAddress(); - return $countAttemptsByIp >= $this->_getAllowedAttemptsFromSameIp(); + $countAttemptsByIp = $this->getResourceModel()->countAttemptsByRemoteAddress(); + return $countAttemptsByIp >= $this->getAllowedAttemptsFromSameIp(); } /** @@ -194,11 +198,11 @@ protected function _isOverLimitIpAttempt() * @param string $login * @return bool */ - protected function _isOverLimitLoginAttempts($login) + private function isOverLimitLoginAttempts($login) { if ($login != false) { - $countAttemptsByLogin = $this->_getResourceModel()->countAttemptsByUserLogin($login); - return $countAttemptsByLogin >= $this->_getAllowedAttemptsForSameLogin(); + $countAttemptsByLogin = $this->getResourceModel()->countAttemptsByUserLogin($login); + return $countAttemptsByLogin >= $this->getAllowedAttemptsForSameLogin(); } return false; } @@ -208,9 +212,9 @@ protected function _isOverLimitLoginAttempts($login) * * @return bool */ - protected function _isUserAuth() + private function isUserAuth() { - return $this->_session->isLoggedIn(); + return $this->session->isLoggedIn(); } /** @@ -220,7 +224,7 @@ protected function _isUserAuth() */ public function isCaseSensitive() { - return (string)$this->_captchaData->getConfig('case_sensitive'); + return (string)$this->captchaData->getConfig('case_sensitive'); } /** @@ -230,8 +234,8 @@ public function isCaseSensitive() */ public function getFont() { - $font = (string)$this->_captchaData->getConfig('font'); - $fonts = $this->_captchaData->getFonts(); + $font = (string)$this->captchaData->getConfig('font'); + $fonts = $this->captchaData->getFonts(); if (isset($fonts[$font])) { $fontPath = $fonts[$font]['path']; @@ -250,14 +254,14 @@ public function getFont() */ public function getExpiration() { - if (!$this->_expiration) { + if (!$this->expiration) { /** * as "timeout" configuration parameter specifies timeout in minutes - we multiply it on 60 to set * expiration in seconds */ - $this->_expiration = (int)$this->_captchaData->getConfig('timeout') * 60; + $this->expiration = (int)$this->captchaData->getConfig('timeout') * 60; } - return $this->_expiration; + return $this->expiration; } /** @@ -277,7 +281,7 @@ public function getTimeout() */ public function getImgDir() { - return $this->_captchaData->getImgDir(); + return $this->captchaData->getImgDir(); } /** @@ -287,7 +291,7 @@ public function getImgDir() */ public function getImgUrl() { - return $this->_captchaData->getImgUrl(); + return $this->captchaData->getImgUrl(); } /** @@ -299,7 +303,7 @@ public function getImgUrl() public function isCorrect($word) { $storedWord = $this->getWord(); - $this->_clearWord(); + $this->clearWord(); if (!$word || !$storedWord) { return false; @@ -330,9 +334,9 @@ public function getImgSrc() */ public function logAttempt($login) { - if ($this->_isEnabled() && in_array($this->_formId, $this->_getTargetForms())) { - $this->_getResourceModel()->logAttempt($login); - if ($this->_isOverLimitLoginAttempts($login)) { + if ($this->isEnabled() && in_array($this->formId, $this->getTargetForms())) { + $this->getResourceModel()->logAttempt($login); + if ($this->isOverLimitLoginAttempts($login)) { $this->setShowCaptchaInSession(true); } } @@ -351,19 +355,20 @@ public function setShowCaptchaInSession($value = true) $value = false; } - $this->_session->setData($this->_getFormIdKey('show_captcha'), $value); + $this->session->setData($this->getFormIdKey('show_captcha'), $value); } /** * Generate word used for captcha render * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _generateWord() + protected function generateWord() { $word = ''; - $symbols = $this->_getSymbols(); - $wordLen = $this->_getWordLen(); + $symbols = $this->getSymbols(); + $wordLen = $this->getWordLen(); for ($i = 0; $i < $wordLen; $i++) { $word .= $symbols[array_rand($symbols)]; } @@ -375,21 +380,22 @@ protected function _generateWord() * * @return array */ - protected function _getSymbols() + private function getSymbols() { - return str_split((string)$this->_captchaData->getConfig('symbols')); + return str_split((string)$this->captchaData->getConfig('symbols')); } /** * Returns length for generating captcha word. This value may be dynamic. * * @return int + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _getWordLen() + public function getWordLen() { $from = 0; $to = 0; - $length = (string)$this->_captchaData->getConfig('length'); + $length = (string)$this->captchaData->getConfig('length'); if (!is_numeric($length)) { if (preg_match('/(\d+)-(\d+)/', $length, $matches)) { $from = (int)$matches[1]; @@ -413,22 +419,22 @@ protected function _getWordLen() * * @return bool */ - protected function _isShowAlways() + private function isShowAlways() { - if ((string)$this->_captchaData->getConfig('mode') == \Magento\Captcha\Helper\Data::MODE_ALWAYS) { + if ((string)$this->captchaData->getConfig('mode') == \Magento\Captcha\Helper\Data::MODE_ALWAYS) { return true; } - if ((string)$this->_captchaData->getConfig( - 'mode' - ) == \Magento\Captcha\Helper\Data::MODE_AFTER_FAIL && $this->_getAllowedAttemptsForSameLogin() == 0 + if ( + (string)$this->captchaData->getConfig('mode') == \Magento\Captcha\Helper\Data::MODE_AFTER_FAIL + && $this->getAllowedAttemptsForSameLogin() == 0 ) { return true; } - $alwaysFor = $this->_captchaData->getConfig('always_for'); + $alwaysFor = $this->captchaData->getConfig('always_for'); foreach ($alwaysFor as $nodeFormId => $isAlwaysFor) { - if ($isAlwaysFor && $this->_formId == $nodeFormId) { + if ($isAlwaysFor && $this->formId == $nodeFormId) { return true; } } @@ -441,9 +447,9 @@ protected function _isShowAlways() * * @return bool */ - protected function _isEnabled() + private function isEnabled() { - return (string)$this->_captchaData->getConfig('enable'); + return (string)$this->captchaData->getConfig('enable'); } /** @@ -453,9 +459,9 @@ protected function _isEnabled() * * @return array */ - protected function _getTargetForms() + private function getTargetForms() { - $formsString = (string)$this->_captchaData->getConfig('forms'); + $formsString = (string)$this->captchaData->getConfig('forms'); return explode(',', $formsString); } @@ -466,7 +472,7 @@ protected function _getTargetForms() */ public function getWord() { - $sessionData = $this->_session->getData($this->_getFormIdKey(self::SESSION_WORD)); + $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD)); return time() < $sessionData['expires'] ? $sessionData['data'] : null; } @@ -476,13 +482,13 @@ public function getWord() * @param string $word * @return $this */ - protected function _setWord($word) + protected function setWord($word) { - $this->_session->setData( - $this->_getFormIdKey(self::SESSION_WORD), + $this->session->setData( + $this->getFormIdKey(self::SESSION_WORD), ['data' => $word, 'expires' => time() + $this->getTimeout()] ); - $this->_word = $word; + $this->word = $word; return $this; } @@ -491,20 +497,21 @@ protected function _setWord($word) * * @return $this */ - protected function _clearWord() + private function clearWord() { - $this->_session->unsetData($this->_getFormIdKey(self::SESSION_WORD)); - $this->_word = null; + $this->session->unsetData($this->getFormIdKey(self::SESSION_WORD)); + $this->word = null; return $this; } /** * Override function to generate less curly captcha that will not cut off * - * @see \Zend_Captcha_Image::_randomSize() + * @see \Zend\Captcha\Image::_randomSize() * @return int + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _randomSize() + protected function randomSize() { return \Magento\Framework\Math\Random::getRandomNumber(280, 300) / 100; } @@ -516,8 +523,11 @@ protected function _randomSize() * * Now deleting old captcha images make crontab script * @see \Magento\Captcha\Cron\DeleteExpiredImages::execute + * + * Added SuppressWarnings since this method is declared in parent class and we can not use other method name. + * @SuppressWarnings(PHPMD.ShortMethodName) */ - protected function _gc() + protected function gc() { //do nothing } @@ -527,8 +537,8 @@ protected function _gc() * * @return \Magento\Captcha\Model\ResourceModel\Log */ - protected function _getResourceModel() + private function getResourceModel() { - return $this->_resLogFactory->create(); + return $this->resLogFactory->create(); } } diff --git a/app/code/Magento/Captcha/Model/ResourceModel/Log.php b/app/code/Magento/Captcha/Model/ResourceModel/Log.php index c38e01e61e41f..f746f1aa9d330 100644 --- a/app/code/Magento/Captcha/Model/ResourceModel/Log.php +++ b/app/code/Magento/Captcha/Model/ResourceModel/Log.php @@ -1,6 +1,6 @@ 1, 'updated_at' => $this->_coreDate->gmtDate() ], - ['count' => new \Zend_Db_Expr('count+1'), 'updated_at'] + ['count' => new \Zend\Db\Sql\Expression('count+1'), 'updated_at'] ); } $ip = $this->_remoteAddress->getRemoteAddress(); @@ -91,7 +92,7 @@ public function logAttempt($login) 'count' => 1, 'updated_at' => $this->_coreDate->gmtDate() ], - ['count' => new \Zend_Db_Expr('count+1'), 'updated_at'] + ['count' => new \Zend\Db\Sql\Expression('count+1'), 'updated_at'] ); } return $this; @@ -102,6 +103,7 @@ public function logAttempt($login) * * @param string $login * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function deleteUserAttempts($login) { @@ -126,6 +128,7 @@ public function deleteUserAttempts($login) * Get count attempts by ip * * @return null|int + * @throws \Magento\Framework\Exception\LocalizedException */ public function countAttemptsByRemoteAddress() { @@ -152,6 +155,7 @@ public function countAttemptsByRemoteAddress() * * @param string $login * @return null|int + * @throws \Magento\Framework\Exception\LocalizedException */ public function countAttemptsByUserLogin($login) { @@ -176,6 +180,7 @@ public function countAttemptsByUserLogin($login) * Delete attempts with expired in update_at time * * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ public function deleteOldAttempts() { diff --git a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php index ff72c7d86a065..b7b8da326ef85 100644 --- a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php +++ b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php @@ -1,6 +1,6 @@ viewMock = $this->getMock(\Magento\Framework\App\ViewInterface::class); $this->layoutMock = $this->getMock(\Magento\Framework\View\LayoutInterface::class); $this->flagMock = $this->getMock(\Magento\Framework\App\ActionFlag::class, [], [], '', false); + $this->serializerMock = $this->getMock( + \Magento\Framework\Serialize\Serializer\Json::class, + [], + [], + '', + false + ); $this->contextMock->expects($this->any())->method('getRequest')->will($this->returnValue($this->requestMock)); $this->contextMock->expects($this->any())->method('getView')->will($this->returnValue($this->viewMock)); @@ -69,7 +81,11 @@ protected function setUp() $this->contextMock->expects($this->any())->method('getActionFlag')->will($this->returnValue($this->flagMock)); $this->viewMock->expects($this->any())->method('getLayout')->will($this->returnValue($this->layoutMock)); - $this->model = new \Magento\Captcha\Controller\Refresh\Index($this->contextMock, $this->captchaHelperMock); + $this->model = new \Magento\Captcha\Controller\Refresh\Index( + $this->contextMock, + $this->captchaHelperMock, + $this->serializerMock + ); } /** @@ -80,6 +96,7 @@ protected function setUp() public function testExecute($formId, $callsNumber) { $content = ['formId' => $formId]; + $imgSource = ['imgSrc' => 'source']; $blockMethods = ['setFormId', 'setIsAjax', 'toHtml']; $blockMock = $this->getMock(\Magento\Captcha\Block\Captcha::class, $blockMethods, [], '', false); @@ -97,8 +114,12 @@ public function testExecute($formId, $callsNumber) $blockMock->expects($this->any())->method('setFormId')->with($formId)->will($this->returnValue($blockMock)); $blockMock->expects($this->any())->method('setIsAjax')->with(true)->will($this->returnValue($blockMock)); $blockMock->expects($this->once())->method('toHtml'); - $this->responseMock->expects($this->once())->method('representJson')->with(json_encode(['imgSrc' => 'source'])); + $this->responseMock->expects($this->once())->method('representJson')->with(json_encode($imgSource)); $this->flagMock->expects($this->once())->method('set')->with('', 'no-postDispatch', true); + $this->serializerMock->expects($this->exactly($callsNumber)) + ->method('unserialize')->will($this->returnValue($content)); + $this->serializerMock->expects($this->once()) + ->method('serialize')->will($this->returnValue(json_encode($imgSource))); $this->model->execute(); } diff --git a/app/code/Magento/Captcha/Test/Unit/Cron/DeleteExpiredImagesTest.php b/app/code/Magento/Captcha/Test/Unit/Cron/DeleteExpiredImagesTest.php index 4bdcceef3a6b1..45f90452bd1fb 100644 --- a/app/code/Magento/Captcha/Test/Unit/Cron/DeleteExpiredImagesTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Cron/DeleteExpiredImagesTest.php @@ -1,6 +1,6 @@ captchaHelperMock->expects($this->once())->method('getCaptcha') ->with('user_login')->will($this->returnValue($this->captchaMock)); $this->formIds = ['user_login']; + $this->serializerMock = $this->getMock( + \Magento\Framework\Serialize\Serializer\Json::class, + [], + [], + '', + false + ); $this->model = new \Magento\Captcha\Model\Customer\Plugin\AjaxLogin( $this->captchaHelperMock, $this->sessionManagerMock, $this->jsonFactoryMock, - $this->formIds + $this->formIds, + $this->serializerMock ); } @@ -92,11 +105,12 @@ public function testAroundExecute() { $username = 'name'; $captchaString = 'string'; - $requestContent = json_encode([ + $requestData = [ 'username' => $username, 'captcha_string' => $captchaString, 'captcha_form_id' => $this->formIds[0] - ]); + ]; + $requestContent = json_encode($requestData); $this->requestMock->expects($this->once())->method('getContent')->will($this->returnValue($requestContent)); $this->captchaMock->expects($this->once())->method('isRequired')->with($username) @@ -104,6 +118,7 @@ public function testAroundExecute() $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); $this->captchaMock->expects($this->once())->method('isCorrect')->with($captchaString) ->will($this->returnValue(true)); + $this->serializerMock->expects(($this->once()))->method('unserialize')->will($this->returnValue($requestData)); $closure = function () { return 'result'; @@ -115,11 +130,12 @@ public function testAroundExecuteIncorrectCaptcha() { $username = 'name'; $captchaString = 'string'; - $requestContent = json_encode([ + $requestData = [ 'username' => $username, 'captcha_string' => $captchaString, 'captcha_form_id' => $this->formIds[0] - ]); + ]; + $requestContent = json_encode($requestData); $this->requestMock->expects($this->once())->method('getContent')->will($this->returnValue($requestContent)); $this->captchaMock->expects($this->once())->method('isRequired')->with($username) @@ -127,6 +143,7 @@ public function testAroundExecuteIncorrectCaptcha() $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); $this->captchaMock->expects($this->once())->method('isCorrect') ->with($captchaString)->will($this->returnValue(false)); + $this->serializerMock->expects(($this->once()))->method('unserialize')->will($this->returnValue($requestData)); $this->sessionManagerMock->expects($this->once())->method('setUsername')->with($username); $this->jsonFactoryMock->expects($this->once())->method('create') @@ -147,7 +164,10 @@ public function testAroundExecuteIncorrectCaptcha() */ public function testAroundExecuteCaptchaIsNotRequired($username, $requestContent) { - $this->requestMock->expects($this->once())->method('getContent')->will($this->returnValue($requestContent)); + $this->requestMock->expects($this->once())->method('getContent') + ->will($this->returnValue(json_encode($requestContent))); + $this->serializerMock->expects(($this->once()))->method('unserialize') + ->will($this->returnValue($requestContent)); $this->captchaMock->expects($this->once())->method('isRequired')->with($username) ->will($this->returnValue(false)); @@ -168,11 +188,11 @@ public function aroundExecuteCaptchaIsNotRequired() return [ [ 'username' => 'name', - 'requestContent' => json_encode(['username' => 'name', 'captcha_string' => 'string']), + 'requestData' => ['username' => 'name', 'captcha_string' => 'string'], ], [ 'username' => null, - 'requestContent' => json_encode(['captcha_string' => 'string']), + 'requestData' => ['captcha_string' => 'string'], ], ]; } diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php index a85941e113520..649bce192614b 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Captcha/etc/adminhtml/events.xml b/app/code/Magento/Captcha/etc/adminhtml/events.xml index 984e5e9e29f4e..7fcadbfd8f2ff 100644 --- a/app/code/Magento/Captcha/etc/adminhtml/events.xml +++ b/app/code/Magento/Captcha/etc/adminhtml/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/adminhtml/routes.xml b/app/code/Magento/Captcha/etc/adminhtml/routes.xml index e06b87beef772..6b6b6717c489a 100644 --- a/app/code/Magento/Captcha/etc/adminhtml/routes.xml +++ b/app/code/Magento/Captcha/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/adminhtml/system.xml b/app/code/Magento/Captcha/etc/adminhtml/system.xml index dc4c737597cce..88f0ae27f91c7 100644 --- a/app/code/Magento/Captcha/etc/adminhtml/system.xml +++ b/app/code/Magento/Captcha/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/config.xml b/app/code/Magento/Captcha/etc/config.xml index a068485910a77..d969626d73144 100644 --- a/app/code/Magento/Captcha/etc/config.xml +++ b/app/code/Magento/Captcha/etc/config.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/crontab.xml b/app/code/Magento/Captcha/etc/crontab.xml index 846a15cbdbec6..d3d6e30e1a03a 100644 --- a/app/code/Magento/Captcha/etc/crontab.xml +++ b/app/code/Magento/Captcha/etc/crontab.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/crontab/di.xml b/app/code/Magento/Captcha/etc/crontab/di.xml index fd57ded2fb92b..f3086b469842b 100644 --- a/app/code/Magento/Captcha/etc/crontab/di.xml +++ b/app/code/Magento/Captcha/etc/crontab/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/di.xml b/app/code/Magento/Captcha/etc/di.xml index 0bb7660f27a6b..db624420ba647 100644 --- a/app/code/Magento/Captcha/etc/di.xml +++ b/app/code/Magento/Captcha/etc/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/events.xml b/app/code/Magento/Captcha/etc/events.xml index 274058ec98e82..4223c4a2a3256 100644 --- a/app/code/Magento/Captcha/etc/events.xml +++ b/app/code/Magento/Captcha/etc/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/frontend/di.xml b/app/code/Magento/Captcha/etc/frontend/di.xml index d15f8d5914998..209f9beb71a04 100644 --- a/app/code/Magento/Captcha/etc/frontend/di.xml +++ b/app/code/Magento/Captcha/etc/frontend/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/frontend/events.xml b/app/code/Magento/Captcha/etc/frontend/events.xml index e1441f0311ee8..dfa0d1b428557 100644 --- a/app/code/Magento/Captcha/etc/frontend/events.xml +++ b/app/code/Magento/Captcha/etc/frontend/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/etc/frontend/routes.xml b/app/code/Magento/Captcha/etc/frontend/routes.xml index 255f4551daf5d..d4bbe64821a91 100644 --- a/app/code/Magento/Captcha/etc/frontend/routes.xml +++ b/app/code/Magento/Captcha/etc/frontend/routes.xml @@ -1,7 +1,7 @@ @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Captcha/etc/module.xml b/app/code/Magento/Captcha/etc/module.xml index 1703142e54133..698604928afb6 100644 --- a/app/code/Magento/Captcha/etc/module.xml +++ b/app/code/Magento/Captcha/etc/module.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/registration.php b/app/code/Magento/Captcha/registration.php index a8fce947e6697..488ac412a8926 100644 --- a/app/code/Magento/Captcha/registration.php +++ b/app/code/Magento/Captcha/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Captcha/view/adminhtml/layout/adminhtml_auth_login.xml b/app/code/Magento/Captcha/view/adminhtml/layout/adminhtml_auth_login.xml index 3cb5ffbbf5ae3..8c093257f1790 100644 --- a/app/code/Magento/Captcha/view/adminhtml/layout/adminhtml_auth_login.xml +++ b/app/code/Magento/Captcha/view/adminhtml/layout/adminhtml_auth_login.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml index 1246f1f4f5df5..37d2ed6fe1238 100644 --- a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml +++ b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/contact_index_index.xml b/app/code/Magento/Captcha/view/frontend/layout/contact_index_index.xml index 9e31eea8aaeba..1460d8fac6974 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/contact_index_index.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/contact_index_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/customer_account_create.xml b/app/code/Magento/Captcha/view/frontend/layout/customer_account_create.xml index 573af66d5bd31..cd72cc5857b83 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/customer_account_create.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/customer_account_create.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/customer_account_edit.xml b/app/code/Magento/Captcha/view/frontend/layout/customer_account_edit.xml index 875479c49954c..9700e88006f10 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/customer_account_edit.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/customer_account_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/customer_account_forgotpassword.xml b/app/code/Magento/Captcha/view/frontend/layout/customer_account_forgotpassword.xml index dc92c7c3548bc..1f25fa040b591 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/customer_account_forgotpassword.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/customer_account_forgotpassword.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/customer_account_login.xml b/app/code/Magento/Captcha/view/frontend/layout/customer_account_login.xml index bcabf0adccc26..3a24e44fd1afe 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/customer_account_login.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/customer_account_login.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/layout/default.xml b/app/code/Magento/Captcha/view/frontend/layout/default.xml index 9d6a234514855..43a770c54c0ca 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/default.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/default.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Captcha/view/frontend/requirejs-config.js b/app/code/Magento/Captcha/view/frontend/requirejs-config.js index 72f7d627b8707..ff1d9f1acc7b1 100644 --- a/app/code/Magento/Captcha/view/frontend/requirejs-config.js +++ b/app/code/Magento/Captcha/view/frontend/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,4 +9,4 @@ var config = { captcha: 'Magento_Captcha/captcha' } } -}; \ No newline at end of file +}; diff --git a/app/code/Magento/Captcha/view/frontend/templates/default.phtml b/app/code/Magento/Captcha/view/frontend/templates/default.phtml index 92c008a0e186a..e028ea19fa8b4 100644 --- a/app/code/Magento/Captcha/view/frontend/templates/default.phtml +++ b/app/code/Magento/Captcha/view/frontend/templates/default.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/Api/AttributeSetFinderInterface.php b/app/code/Magento/Catalog/Api/AttributeSetFinderInterface.php index 4dabce697b30f..c7df36623ff83 100644 --- a/app/code/Magento/Catalog/Api/AttributeSetFinderInterface.php +++ b/app/code/Magento/Catalog/Api/AttributeSetFinderInterface.php @@ -1,7 +1,7 @@ (int) Entity identified or entity link field. + * 'value' => (float) Special price value. + * 'store_id' => (int) Store Id. + * 'sku' => (string) Product SKU. + * 'price_from' => (string) Special price from date value in UTC. + * 'price_to' => (string) Special price to date value in UTC. + * ] + */ + public function get(array $skus); + + /** + * Update product special prices. + * + * @param array $prices + * $prices = [ + * 'entity_id' => (int) Entity identified or entity link field. Required. + * 'attribute_id' => (int) Special price attribute Id. Required. + * 'store_id' => (int) Store Id. Required. + * 'value' => (float) Special price value. Required. + * 'price_from' => (string) Special price from date value in Y-m-d H:i:s format in UTC. Optional. + * 'price_to' => (string) Special price to date value in Y-m-d H:i:s format in UTC. Optional. + * ]; + * @return bool + * @throws \Magento\Framework\Exception\CouldNotSaveException Thrown if error occurred during price save. + */ + public function update(array $prices); + + /** + * Delete product special prices. + * + * @param array $prices + * $prices = [ + * 'entity_id' => (int) Entity identified or entity link field. Required. + * 'attribute_id' => (int) Special price attribute Id. Required. + * 'store_id' => (int) Store Id. Required. + * 'value' => (float) Special price value. Required. + * 'price_from' => (string) Special price from date value in Y-m-d H:i:s format in UTC. Optional. + * 'price_to' => (string) Special price to date value in Y-m-d H:i:s format in UTC. Optional. + * ]; + * @return bool + * @throws \Magento\Framework\Exception\CouldNotDeleteException Thrown if error occurred during price delete. + */ + public function delete(array $prices); +} diff --git a/app/code/Magento/Catalog/Api/SpecialPriceStorageInterface.php b/app/code/Magento/Catalog/Api/SpecialPriceStorageInterface.php new file mode 100644 index 0000000000000..fe234ab675b3f --- /dev/null +++ b/app/code/Magento/Catalog/Api/SpecialPriceStorageInterface.php @@ -0,0 +1,49 @@ +serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); + } + /** * @return $this */ @@ -19,6 +47,7 @@ public function _construct() /** * @return string + * @deprecated */ public function getProductsJSON() { @@ -30,6 +59,6 @@ public function getProductsJSON() $result[$id] = $product->toArray(['qty', 'position']); } } - return $result ? \Zend_Json::encode($result) : '{}'; + return $result ? $this->serializer->serialize($result) : '{}'; } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts.php index b6746106aea14..c76835456bf10 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts.php @@ -1,6 +1,6 @@ getTypeInstance()->hasRequiredOptions($product)) { + if (!$product->getTypeInstance()->isPossibleBuyFromList($product)) { if (!isset($additional['_escape'])) { $additional['_escape'] = true; } diff --git a/app/code/Magento/Catalog/Block/Product/AwareInterface.php b/app/code/Magento/Catalog/Block/Product/AwareInterface.php index de9564075531c..deec15b976ba3 100644 --- a/app/code/Magento/Catalog/Block/Product/AwareInterface.php +++ b/app/code/Magento/Catalog/Block/Product/AwareInterface.php @@ -1,6 +1,6 @@ _productCollection === null) { - $layer = $this->getLayer(); - /* @var $layer \Magento\Catalog\Model\Layer */ - if ($this->getShowRootCategory()) { - $this->setCategoryId($this->_storeManager->getStore()->getRootCategoryId()); - } - - // if this is a product view page - if ($this->_coreRegistry->registry('product')) { - // get collection of categories this product is associated with - $categories = $this->_coreRegistry->registry('product') - ->getCategoryCollection()->setPage(1, 1) - ->load(); - // if the product is associated with any category - if ($categories->count()) { - // show products from this category - $this->setCategoryId(current($categories->getIterator())); - } - } - - $origCategory = null; - if ($this->getCategoryId()) { - try { - $category = $this->categoryRepository->get($this->getCategoryId()); - } catch (NoSuchEntityException $e) { - $category = null; - } - - if ($category) { - $origCategory = $layer->getCurrentCategory(); - $layer->setCurrentCategory($category); - } - } - $this->_productCollection = $layer->getProductCollection(); - - $this->prepareSortableFieldsByCategory($layer->getCurrentCategory()); - - if ($origCategory) { - $layer->setCurrentCategory($origCategory); - } + $this->_productCollection = $this->initializeProductCollection(); } return $this->_productCollection; @@ -170,39 +145,9 @@ public function getMode() */ protected function _beforeToHtml() { - $toolbar = $this->getToolbarBlock(); - - // called prepare sortable parameters $collection = $this->_getProductCollection(); - - // use sortable parameters - $orders = $this->getAvailableOrders(); - if ($orders) { - $toolbar->setAvailableOrders($orders); - } - $sort = $this->getSortBy(); - if ($sort) { - $toolbar->setDefaultOrder($sort); - } - $dir = $this->getDefaultDirection(); - if ($dir) { - $toolbar->setDefaultDirection($dir); - } - $modes = $this->getModes(); - if ($modes) { - $toolbar->setModes($modes); - } - - // set collection to toolbar and apply sort - $toolbar->setCollection($collection); - - $this->setChild('toolbar', $toolbar); - $this->_eventManager->dispatch( - 'catalog_block_product_list_collection', - ['collection' => $this->_getProductCollection()] - ); - - $this->_getProductCollection()->load(); + $this->configureToolbar($this->getToolbarBlock(), $collection); + $collection->load(); return parent::_beforeToHtml(); } @@ -210,7 +155,7 @@ protected function _beforeToHtml() /** * Retrieve Toolbar block * - * @return \Magento\Catalog\Block\Product\ProductList\Toolbar + * @return Toolbar */ public function getToolbarBlock() { @@ -379,4 +324,107 @@ protected function getPriceRender() { return $this->getLayout()->getBlock('product.price.render.default'); } + + /** + * Configures product collection from a layer and returns its instance. + * + * Also in the scope of a product collection configuration, this method initiates configuration of Toolbar. + * The reason to do this is because we have a bunch of legacy code + * where Toolbar configures several options of a collection and therefore this block depends on the Toolbar. + * + * This dependency leads to a situation where Toolbar sometimes called to configure a product collection, + * and sometimes not. + * + * To unify this behavior and prevent potential bugs this dependency is explicitly called + * when product collection initialized. + * + * @return Collection + */ + private function initializeProductCollection() + { + $layer = $this->getLayer(); + /* @var $layer \Magento\Catalog\Model\Layer */ + if ($this->getShowRootCategory()) { + $this->setCategoryId($this->_storeManager->getStore()->getRootCategoryId()); + } + + // if this is a product view page + if ($this->_coreRegistry->registry('product')) { + // get collection of categories this product is associated with + $categories = $this->_coreRegistry->registry('product') + ->getCategoryCollection()->setPage(1, 1) + ->load(); + // if the product is associated with any category + if ($categories->count()) { + // show products from this category + $this->setCategoryId(current($categories->getIterator())); + } + } + + $origCategory = null; + if ($this->getCategoryId()) { + try { + $category = $this->categoryRepository->get($this->getCategoryId()); + } catch (NoSuchEntityException $e) { + $category = null; + } + + if ($category) { + $origCategory = $layer->getCurrentCategory(); + $layer->setCurrentCategory($category); + } + } + $collection = $layer->getProductCollection(); + + $this->prepareSortableFieldsByCategory($layer->getCurrentCategory()); + + if ($origCategory) { + $layer->setCurrentCategory($origCategory); + } + + $toolbar = $this->getToolbarBlock(); + $this->configureToolbar($toolbar, $collection); + + $this->_eventManager->dispatch( + 'catalog_block_product_list_collection', + ['collection' => $collection] + ); + + return $collection; + } + + /** + * Configures the Toolbar block with options from this block and configured product collection. + * + * The purpose of this method is the one-way sharing of different sorting related data + * between this block, which is responsible for product list rendering, + * and the Toolbar block, whose responsibility is a rendering of these options. + * + * @param ProductList\Toolbar $toolbar + * @param Collection $collection + * @return void + */ + private function configureToolbar(Toolbar $toolbar, Collection $collection) + { + // use sortable parameters + $orders = $this->getAvailableOrders(); + if ($orders) { + $toolbar->setAvailableOrders($orders); + } + $sort = $this->getSortBy(); + if ($sort) { + $toolbar->setDefaultOrder($sort); + } + $dir = $this->getDefaultDirection(); + if ($dir) { + $toolbar->setDefaultDirection($dir); + } + $modes = $this->getModes(); + if ($modes) { + $toolbar->setModes($modes); + } + // set collection to toolbar and apply sort + $toolbar->setCollection($collection); + $this->setChild('toolbar', $toolbar); + } } diff --git a/app/code/Magento/Catalog/Block/Product/NewProduct.php b/app/code/Magento/Catalog/Block/Product/NewProduct.php index 73a9df1bf3822..11bb639ac2a5b 100644 --- a/app/code/Magento/Catalog/Block/Product/NewProduct.php +++ b/app/code/Magento/Catalog/Block/Product/NewProduct.php @@ -1,6 +1,6 @@ $product->getId(), 'priceFormat' => $this->_localeFormat->getPriceFormat() - ]; + ]; return $this->_jsonEncoder->encode($config); } $tierPrices = []; $tierPricesList = $product->getPriceInfo()->getPrice('tier_price')->getTierPriceList(); foreach ($tierPricesList as $tierPrice) { - $tierPrices[] = $this->priceCurrency->convert($tierPrice['price']->getValue()); + $tierPrices[] = $tierPrice['price']->getValue(); } $config = [ - 'productId' => $product->getId(), + 'productId' => $product->getId(), 'priceFormat' => $this->_localeFormat->getPriceFormat(), - 'prices' => [ - 'oldPrice' => [ - 'amount' => $this->priceCurrency->convert( - $product->getPriceInfo()->getPrice('regular_price')->getAmount()->getValue() - ), + 'prices' => [ + 'oldPrice' => [ + 'amount' => $product->getPriceInfo()->getPrice('regular_price')->getAmount()->getValue(), 'adjustments' => [] ], - 'basePrice' => [ - 'amount' => $this->priceCurrency->convert( - $product->getPriceInfo()->getPrice('final_price')->getAmount()->getBaseAmount() - ), + 'basePrice' => [ + 'amount' => $product->getPriceInfo()->getPrice('final_price')->getAmount()->getBaseAmount(), 'adjustments' => [] ], 'finalPrice' => [ - 'amount' => $this->priceCurrency->convert( - $product->getPriceInfo()->getPrice('final_price')->getAmount()->getValue() - ), + 'amount' => $product->getPriceInfo()->getPrice('final_price')->getAmount()->getValue(), 'adjustments' => [] ] ], - 'idSuffix' => '_clone', - 'tierPrices' => $tierPrices + 'idSuffix' => '_clone', + 'tierPrices' => $tierPrices ]; $responseObject = new \Magento\Framework\DataObject(); diff --git a/app/code/Magento/Catalog/Block/Product/View/AbstractView.php b/app/code/Magento/Catalog/Block/Product/View/AbstractView.php index c8cc393c66006..5daaff14ed733 100644 --- a/app/code/Magento/Catalog/Block/Product/View/AbstractView.php +++ b/app/code/Magento/Catalog/Block/Product/View/AbstractView.php @@ -1,6 +1,6 @@ setData( 'medium_image_url', - $this->_imageHelper->init($product, 'product_page_image_medium') - ->constrainOnly(true)->keepAspectRatio(true)->keepFrame(false) + $this->_imageHelper->init($product, 'product_page_image_medium_no_frame') ->setImageFile($image->getFile()) ->getUrl() ); $image->setData( 'large_image_url', - $this->_imageHelper->init($product, 'product_page_image_large') - ->constrainOnly(true)->keepAspectRatio(true)->keepFrame(false) + $this->_imageHelper->init($product, 'product_page_image_large_no_frame') ->setImageFile($image->getFile()) ->getUrl() ); diff --git a/app/code/Magento/Catalog/Block/Product/View/Options.php b/app/code/Magento/Catalog/Block/Product/View/Options.php index 349352513bec6..5f045fdacfc3f 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options.php @@ -1,6 +1,6 @@ getRequest()->getParam('id', false); + $categoryId = $this->resolveCategoryId(); $storeId = (int)$this->getRequest()->getParam('store'); $category = $this->_objectManager->create(\Magento\Catalog\Model\Category::class); $category->setStoreId($storeId); @@ -62,6 +62,18 @@ protected function _initCategory($getRootInstead = false) return $category; } + /** + * Resolve Category Id (from get or from post) + * + * @return int + */ + private function resolveCategoryId() + { + $categoryId = (int)$this->getRequest()->getParam('id', false); + + return $categoryId ?: (int)$this->getRequest()->getParam('entity_id', false); + } + /** * Build response for ajax request * diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php index 663bf6a5b0915..29ffcdf1682f8 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php @@ -1,7 +1,7 @@ ajaxRequestResponse($category, $resultPage); } + $resultPageTitle = $categoryId ? $category->getName() . ' (ID: ' . $categoryId . ')' : __('Categories'); $resultPage->setActiveMenu('Magento_Catalog::catalog_categories'); $resultPage->getConfig()->getTitle()->prepend(__('Categories')); - $resultPage->getConfig()->getTitle()->prepend($categoryId ? $category->getName() : __('Categories')); + $resultPage->getConfig()->getTitle()->prepend($resultPageTitle); $resultPage->addBreadcrumb(__('Manage Catalog Categories'), __('Manage Categories')); $block = $resultPage->getLayout()->getBlock('catalog.wysiwyg.js'); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Grid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Grid.php index 4b2fa125ac137..0eef32b191abc 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Grid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Grid.php @@ -1,7 +1,7 @@ _request->getParam('param_name', 'image'); + try { - $result = $this->imageUploader->saveFileToTmpDir('image'); + $result = $this->imageUploader->saveFileToTmpDir($imageId); $result['cookie'] = [ 'name' => $this->_getSession()->getName(), diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php index 5640873acbd6b..7c02a5daa5d43 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php @@ -1,7 +1,7 @@ messageManager->addSuccess(__('You moved the category')); + $this->messageManager->addSuccess(__('You moved the category.')); } $block->setMessages($this->messageManager->getMessages(true)); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php index 4202d21089563..f8aa64e6bdc1e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php @@ -1,7 +1,7 @@ resultRawFactory = $resultRawFactory; $this->resultJsonFactory = $resultJsonFactory; $this->layoutFactory = $layoutFactory; $this->storeManager = $storeManager; + $this->eavConfig = $eavConfig + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Eav\Model\Config::class); } /** * Filter category data * + * @deprecated * @param array $rawData * @return array */ protected function _filterCategoryPostData(array $rawData) { $data = $rawData; - // @todo It is a workaround to prevent saving this data in category model and it has to be refactored in future if (isset($data['image']) && is_array($data['image'])) { if (!empty($data['image']['delete'])) { $data['image'] = null; @@ -126,7 +136,7 @@ public function execute() $this->storeManager->setCurrentStore($store->getCode()); $parentId = isset($categoryPostData['parent']) ? $categoryPostData['parent'] : null; if ($categoryPostData) { - $category->addData($this->_filterCategoryPostData($categoryPostData)); + $category->addData($categoryPostData); if ($isNewCategory) { $parentCategory = $this->getParentCategory($parentId, $storeId); $category->setPath($parentCategory->getPath()); @@ -248,18 +258,30 @@ public function execute() } /** - * Image data preprocessing + * Sets image attribute data to false if image was removed * * @param array $data - * * @return array */ public function imagePreprocessing($data) { - if (empty($data['image'])) { - unset($data['image']); - $data['image']['delete'] = true; + $entityType = $this->eavConfig->getEntityType(CategoryAttributeInterface::ENTITY_TYPE_CODE); + + foreach ($entityType->getAttributeCollection() as $attributeModel) { + $attributeCode = $attributeModel->getAttributeCode(); + $backendModel = $attributeModel->getBackend(); + + if (isset($data[$attributeCode])) { + continue; + } + + if (!$backendModel instanceof \Magento\Catalog\Model\Category\Attribute\Backend\Image) { + continue; + } + + $data[$attributeCode] = false; } + return $data; } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/SuggestCategories.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/SuggestCategories.php index 9ab7e84cead06..2e34cf244ef9a 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/SuggestCategories.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/SuggestCategories.php @@ -1,7 +1,7 @@ attributeHelper->getStoreWebsiteId($storeId) ); if (!$stockItemDo->getProductId()) { - $inventoryData[] = $productId; + $inventoryData['product_id'] = $productId; } $stockItemId = $stockItemDo->getId(); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php index 803e2672f4249..51855cf0618ce 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php @@ -1,7 +1,7 @@ getSearchCriteriaBuilder() ->addFilter('attribute_set_id', $attributeSet->getAttributeSetId()) ->addFilter('attribute_group_code', $groupCode) - ->addSortOrder($this->getSortOrderBuilder()->setAscendingDirection()->create()) ->setPageSize(1) ->create(); @@ -252,18 +245,6 @@ private function getSearchCriteriaBuilder() return $this->searchCriteriaBuilder; } - /** - * @return SortOrderBuilder - */ - private function getSortOrderBuilder() - { - if (null === $this->sortOrderBuilder) { - $this->sortOrderBuilder = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Api\SortOrderBuilder::class); - } - return $this->sortOrderBuilder; - } - /** * @return AttributeManagementInterface */ diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGrid.php index 7a0190d99db51..3b370edc27008 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGrid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AlertsPriceGrid.php @@ -1,7 +1,7 @@ $checkboxValue) { - if (!$checkboxValue) { - unset($productData['website_ids'][$websiteId]); - } - } + $productData['website_ids'] = $this->filterWebsiteIds($productData['website_ids']); $wasLockedMedia = false; if ($product->isLockedAttribute('media')) { @@ -199,6 +194,9 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra $customOptions = []; foreach ($options as $customOptionData) { if (empty($customOptionData['is_delete'])) { + if (empty($customOptionData['option_id'])) { + $customOptionData['option_id'] = null; + } if (isset($customOptionData['values'])) { $customOptionData['values'] = array_filter($customOptionData['values'], function ($valueData) { return empty($valueData['is_delete']); @@ -206,7 +204,6 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra } $customOption = $this->getCustomOptionFactory()->create(['data' => $customOptionData]); $customOption->setProductSku($product->getSku()); - $customOption->setOptionId(null); $customOptions[] = $customOption; } } @@ -255,7 +252,7 @@ protected function setProductLinks(\Magento\Catalog\Model\Product $product) foreach ($linkTypes as $linkType => $readonly) { if (isset($links[$linkType]) && !$readonly) { - foreach ((array) $links[$linkType] as $linkData) { + foreach ((array)$links[$linkType] as $linkData) { if (empty($linkData['id'])) { continue; } @@ -321,9 +318,11 @@ public function mergeProductOptions($productOptions, $overwriteOptions) if (isset($option['values']) && isset($overwriteOptions[$optionId]['values'])) { foreach ($option['values'] as $valueIndex => $value) { - $valueId = $value['option_type_id']; - $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); - $option['values'][$valueIndex] = $value; + if (isset($value['option_type_id'])) { + $valueId = $value['option_type_id']; + $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); + $option['values'][$valueIndex] = $value; + } } } @@ -347,6 +346,9 @@ private function overwriteValue($optionId, $option, $overwriteOptions) foreach ($overwriteOptions[$optionId] as $fieldName => $overwrite) { if ($overwrite && isset($option[$fieldName]) && isset($option['default_' . $fieldName])) { $option[$fieldName] = $option['default_' . $fieldName]; + if ('title' == $fieldName) { + $option['is_delete_store_title'] = 1; + } } } } @@ -415,4 +417,23 @@ private function getDateTimeFilter() } return $this->dateTimeFilter; } + + /** + * Remove ids of non selected websites from $websiteIds array and return filtered data + * $websiteIds parameter expects array with website ids as keys and 1 (selected) or 0 (non selected) as values + * Only one id (default website ID) will be set to $websiteIds array when the single store mode is turned on + * + * @param array $websiteIds + * @return array + */ + private function filterWebsiteIds($websiteIds) + { + if (!$this->storeManager->isSingleStoreMode()) { + $websiteIds = array_filter((array)$websiteIds); + } else { + $websiteIds[$this->storeManager->getWebsite(true)->getId()] = 1; + } + + return $websiteIds; + } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/HandlerFactory.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/HandlerFactory.php index f282df484b076..d720c26a8a905 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/HandlerFactory.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/HandlerFactory.php @@ -1,6 +1,6 @@ getAllIds(); $storeId = (int) $this->getRequest()->getParam('store', 0); $status = (int) $this->getRequest()->getParam('status'); + $filters = (array)$this->getRequest()->getParam('filters', []); + + if (isset($filters['store_id'])) { + $storeId = (int)$filters['store_id']; + } try { $this->_validateMassStatus($productIds, $status); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php index a5153a7342674..62fc127b43a3d 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php @@ -1,7 +1,7 @@ save(); $this->messageManager->addSuccess(__('You saved the attribute set.')); + } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $hasError = true; } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); $hasError = true; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/SetGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/SetGrid.php index c0f7246f334f0..b9226a622f598 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/SetGrid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/SetGrid.php @@ -1,7 +1,7 @@ addPageLayoutHandles(['type' => $parentType]); + $page->addPageLayoutHandles(['type' => $parentType], null, false); } - $page->addPageLayoutHandles(['type' => $type, 'id' => $category->getId()]); + $page->addPageLayoutHandles(['type' => $type], null, false); + $page->addPageLayoutHandles(['id' => $category->getId()]); // apply custom layout update once layout is loaded $layoutUpdates = $settings->getLayoutUpdates(); if ($layoutUpdates && is_array($layoutUpdates)) { foreach ($layoutUpdates as $layoutUpdate) { $page->addUpdate($layoutUpdate); - $page->addPageLayoutHandles(['layout_update' => md5($layoutUpdate)]); + $page->addPageLayoutHandles(['layout_update' => md5($layoutUpdate)], null, false); } } diff --git a/app/code/Magento/Catalog/Controller/Index/Index.php b/app/code/Magento/Catalog/Controller/Index/Index.php index 7512affdef3c9..4b83aee1a061e 100644 --- a/app/code/Magento/Catalog/Controller/Index/Index.php +++ b/app/code/Magento/Catalog/Controller/Index/Index.php @@ -1,6 +1,6 @@ resource = $resource; + $this->attributeRepository = $attributeRepository; + $this->scopeConfig = $scopeConfig; + } + + /** + * Delete all price values for non-admin stores if PRICE_SCOPE is global + * + * @return void + */ + public function execute() + { + $priceScope = $this->scopeConfig->getValue(Store::XML_PATH_PRICE_SCOPE); + if ($priceScope == Store::PRICE_SCOPE_GLOBAL) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $priceAttribute */ + $priceAttribute = $this->attributeRepository + ->get(ProductAttributeInterface::ENTITY_TYPE_CODE, ProductAttributeInterface::CODE_PRICE); + $connection = $this->resource->getConnection(); + $conditions = [ + $connection->quoteInto('attribute_id = ?', $priceAttribute->getId()), + $connection->quoteInto('store_id != ?', Store::DEFAULT_STORE_ID), + ]; + + $connection->delete( + $priceAttribute->getBackend()->getTable(), + $conditions + ); + } + } +} diff --git a/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php b/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php index e3a63e0367328..5e04b026db3d4 100644 --- a/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php +++ b/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php @@ -1,6 +1,6 @@ _getModel()->setDestinationSubdir($this->getType()); - $this->_getModel()->setWidth($this->getWidth()); $this->_getModel()->setHeight($this->getHeight()); @@ -241,25 +240,25 @@ protected function setWatermarkProperties() { $this->setWatermark( $this->scopeConfig->getValue( - "design/watermark/{$this->_getModel()->getDestinationSubdir()}_image", + "design/watermark/{$this->getType()}_image", \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) ); $this->setWatermarkImageOpacity( $this->scopeConfig->getValue( - "design/watermark/{$this->_getModel()->getDestinationSubdir()}_imageOpacity", + "design/watermark/{$this->getType()}_imageOpacity", \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) ); $this->setWatermarkPosition( $this->scopeConfig->getValue( - "design/watermark/{$this->_getModel()->getDestinationSubdir()}_position", + "design/watermark/{$this->getType()}_position", \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) ); $this->setWatermarkSize( $this->scopeConfig->getValue( - "design/watermark/{$this->_getModel()->getDestinationSubdir()}_size", + "design/watermark/{$this->getType()}_size", \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) ); @@ -500,10 +499,7 @@ protected function initBaseFile() protected function isScheduledActionsAllowed() { $model = $this->_getModel(); - if ($model->isBaseFilePlaceholder() - && $model->getNewFile() === true - || $model->isCached() - ) { + if ($model->isBaseFilePlaceholder() || $model->isCached()) { return false; } return true; diff --git a/app/code/Magento/Catalog/Helper/Output.php b/app/code/Magento/Catalog/Helper/Output.php index 0b4b8a0dab9a6..a738b3fddfa07 100644 --- a/app/code/Magento/Catalog/Helper/Output.php +++ b/app/code/Magento/Catalog/Helper/Output.php @@ -1,6 +1,6 @@ getEncodedUrl($this->_getUrl('catalog/product_compare')); $data = [ - \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => $listCleanUrl, - 'product' => $product->getId() + \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', + 'product' => $product->getId(), + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?') ]; return $this->postHelper->getPostData($this->getRemoveUrl(), $data); } @@ -253,9 +254,10 @@ public function getClearListUrl() */ public function getPostDataClearList() { - $refererUrl = $this->_getRequest()->getServer('HTTP_REFERER'); $params = [ - \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => $this->urlEncoder->encode($refererUrl) + \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; return $this->postHelper->getPostData($this->getClearListUrl(), $params); } diff --git a/app/code/Magento/Catalog/Helper/Product/Composite.php b/app/code/Magento/Catalog/Helper/Product/Composite.php index fcd710ad1ec84..5e519540c78ca 100644 --- a/app/code/Magento/Catalog/Helper/Product/Composite.php +++ b/app/code/Magento/Catalog/Helper/Product/Composite.php @@ -1,6 +1,6 @@ _productOptionFactory = $productOptionFactory; $this->filter = $filter; $this->string = $string; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); parent::__construct($context); } @@ -105,7 +115,7 @@ public function getCustomOptions(\Magento\Catalog\Model\Product\Configuration\It $addOptions = $item->getOptionByCode('additional_options'); if ($addOptions) { - $options = array_merge($options, unserialize($addOptions->getValue())); + $options = array_merge($options, $this->serializer->unserialize($addOptions->getValue())); } return $options; diff --git a/app/code/Magento/Catalog/Helper/Product/Configuration/ConfigurationInterface.php b/app/code/Magento/Catalog/Helper/Product/Configuration/ConfigurationInterface.php index 0e47bc998e4bb..26b4e9e180327 100644 --- a/app/code/Magento/Catalog/Helper/Product/Configuration/ConfigurationInterface.php +++ b/app/code/Magento/Catalog/Helper/Product/Configuration/ConfigurationInterface.php @@ -1,6 +1,6 @@ getBeforeHandles()) { foreach ($params->getBeforeHandles() as $handle) { - $resultPage->addPageLayoutHandles( - ['id' => $product->getId(), 'sku' => $urlSafeSku, 'type' => $product->getTypeId()], - $handle - ); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); } } - $resultPage->addPageLayoutHandles( - ['id' => $product->getId(), 'sku' => $urlSafeSku, 'type' => $product->getTypeId()] - ); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); if ($params && $params->getAfterHandles()) { foreach ($params->getAfterHandles() as $handle) { - $resultPage->addPageLayoutHandles( - ['id' => $product->getId(), 'sku' => $urlSafeSku, 'type' => $product->getTypeId()], - $handle - ); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); } } diff --git a/app/code/Magento/Catalog/Model/AbstractModel.php b/app/code/Magento/Catalog/Model/AbstractModel.php index 40188c4efc9df..ccaa5829cd717 100644 --- a/app/code/Magento/Catalog/Model/AbstractModel.php +++ b/app/code/Magento/Catalog/Model/AbstractModel.php @@ -1,6 +1,6 @@ getAttribute()->getName(); $startDate = $object->getData($attributeName); - if ($startDate === false) { - return false; - } - if ($startDate == '' && $object->getSpecialPrice()) { - $startDate = $this->_localeDate->date(); - } return $startDate; } diff --git a/app/code/Magento/Catalog/Model/Attribute/Config.php b/app/code/Magento/Catalog/Model/Attribute/Config.php index 1b939c9118c35..4337bcb2a4707 100644 --- a/app/code/Magento/Catalog/Model/Attribute/Config.php +++ b/app/code/Magento/Catalog/Model/Attribute/Config.php @@ -2,7 +2,7 @@ /** * High-level interface for catalog attributes data that hides format from the client code * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Attribute; diff --git a/app/code/Magento/Catalog/Model/Attribute/Config/Converter.php b/app/code/Magento/Catalog/Model/Attribute/Config/Converter.php index 0c9f79d78183b..04b1bf72c9280 100644 --- a/app/code/Magento/Catalog/Model/Attribute/Config/Converter.php +++ b/app/code/Magento/Catalog/Model/Attribute/Config/Converter.php @@ -2,7 +2,7 @@ /** * Converter of attributes configuration from \DOMDocument to array * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Attribute\Config; diff --git a/app/code/Magento/Catalog/Model/Attribute/Config/Data.php b/app/code/Magento/Catalog/Model/Attribute/Config/Data.php index 032970a7461b6..831b7e211a92d 100644 --- a/app/code/Magento/Catalog/Model/Attribute/Config/Data.php +++ b/app/code/Magento/Catalog/Model/Attribute/Config/Data.php @@ -1,22 +1,31 @@ getImage(); + $image = $this->getData($attributeCode); if ($image) { if (is_string($image)) { $url = $this->_storeManager->getStore()->getBaseUrl( diff --git a/app/code/Magento/Catalog/Model/Category/Attribute.php b/app/code/Magento/Catalog/Model/Category/Attribute.php index 51c3b87937cf7..94a108c063f51 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute.php @@ -1,6 +1,6 @@ getAttribute()->getName(); + $value = $object->getData($attributeName); + + if ($imageName = $this->getUploadedImageName($value)) { + $object->setData($this->additionalData . $attributeName, $value); + $object->setData($attributeName, $imageName); + } else if (!is_string($value)) { + $object->setData($attributeName, ''); + } + + return parent::beforeSave($object); + } + + /** * @return \Magento\Catalog\Model\ImageUploader * * @deprecated @@ -79,10 +112,10 @@ public function __construct( private function getImageUploader() { if ($this->imageUploader === null) { - $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Catalog\CategoryImageUpload::class - ); + $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\CategoryImageUpload::class); } + return $this->imageUploader; } @@ -94,15 +127,16 @@ private function getImageUploader() */ public function afterSave($object) { - $image = $object->getData($this->getAttribute()->getName(), null); + $value = $object->getData($this->additionalData . $this->getAttribute()->getName()); - if ($image !== null) { + if ($imageName = $this->getUploadedImageName($value)) { try { - $this->getImageUploader()->moveFileFromTmp($image); + $this->getImageUploader()->moveFileFromTmp($imageName); } catch (\Exception $e) { $this->_logger->critical($e); } } + return $this; } } diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Sortby.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Sortby.php index 516ce756dff97..d18146ebae85e 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Sortby.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Sortby.php @@ -1,6 +1,6 @@ storeManager = $storeManager; $this->request = $request; $this->categoryFactory = $categoryFactory; + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); - $this->meta = $this->prepareMeta($this->meta); + } + + /** + * @inheritdoc + */ + public function getMeta() + { + $meta = parent::getMeta(); + $meta = $this->prepareMeta($meta); + + $category = $this->getCurrentCategory(); + + if ($category) { + $meta = $this->addUseDefaultValueCheckbox($category, $meta); + $meta = $this->resolveParentInheritance($category, $meta); + } + + return $meta; + } + + /** + * @param Category $category + * @param array $meta + * @return array + */ + private function addUseDefaultValueCheckbox(Category $category, array $meta) + { + /** @var EavAttributeInterface $attribute */ + foreach ($category->getAttributes() as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + $canDisplayUseDefault = $attribute->getScope() != EavAttributeInterface::SCOPE_GLOBAL_TEXT + && $category->getId() + && $category->getStoreId(); + $attributePath = $this->getArrayManager()->findPath($attributeCode, $meta); + + if ( + !$attributePath + || !$canDisplayUseDefault + || in_array($attributeCode, $this->elementsWithUseConfigSetting) + ) { + continue; + } + + $meta = $this->getArrayManager()->merge( + [$attributePath, 'arguments/data/config'], + $meta, + [ + 'service' => [ + 'template' => 'ui/form/element/helper/service', + ], + 'disabled' => !$this->getScopeOverriddenValue()->containsValue( + CategoryInterface::class, + $category, + $attributeCode, + $this->request->getParam($this->requestScopeFieldName, Store::DEFAULT_STORE_ID) + ) + ] + ); + } + + return $meta; + } + + /** + * Removes not necessary inheritance fields + * + * @param Category $category + * @param array $meta + * @return array + */ + private function resolveParentInheritance(Category $category, array $meta) + { + if (!$category->getParentId() || !$this->getArrayManager()->findPath('custom_use_parent_settings', $meta)) { + return $meta; + } + + $meta = $this->getArrayManager()->merge( + [$this->getArrayManager()->findPath('custom_use_parent_settings', $meta), 'arguments/data/config'], + $meta, + ['visible' => false] + ); + + return $meta; } /** @@ -203,14 +308,10 @@ public function getData() $category = $this->getCurrentCategory(); if ($category) { $categoryData = $category->getData(); - $categoryData = $this->addUseDefaultSettings($category, $categoryData); $categoryData = $this->addUseConfigSettings($categoryData); $categoryData = $this->filterFields($categoryData); - if (isset($categoryData['image'])) { - unset($categoryData['image']); - $categoryData['image'][0]['name'] = $category->getData('image'); - $categoryData['image'][0]['url'] = $category->getImageUrl(); - } + $categoryData = $this->convertValues($category, $categoryData); + $this->loadedData[$category->getId()] = $categoryData; } return $this->loadedData; @@ -294,6 +395,7 @@ protected function addUseConfigSettings($categoryData) * @param \Magento\Catalog\Model\Category $category * @param array $categoryData * @return array + * @deprecated */ protected function addUseDefaultSettings($category, $categoryData) { @@ -371,6 +473,39 @@ protected function filterFields($categoryData) return array_diff_key($categoryData, array_flip($this->ignoreFields)); } + /** + * Converts category image data to acceptable for rendering format + * + * @param \Magento\Catalog\Model\Category $category + * @param array $categoryData + * @return array + */ + private function convertValues($category, $categoryData) + { + foreach ($category->getAttributes() as $attributeCode => $attribute) { + if (!isset($categoryData[$attributeCode])) { + continue; + } + + if ($attribute->getBackend() instanceof ImageBackendModel) { + unset($categoryData[$attributeCode]); + + $fileName = $category->getData($attributeCode); + if ($this->getFileInfo()->isExist($fileName)) { + $stat = $this->getFileInfo()->getStat($fileName); + $mime = $this->getFileInfo()->getMimeType($fileName); + + $categoryData[$attributeCode][0]['name'] = $fileName; + $categoryData[$attributeCode][0]['url'] = $category->getImageUrl($attributeCode); + $categoryData['image'][0]['size'] = isset($stat) ? $stat['size'] : 0; + $categoryData['image'][0]['type'] = $mime; + } + } + } + + return $categoryData; + } + /** * Category's fields default values * @@ -383,15 +518,6 @@ public function getDefaultMetaData($result) $result['use_config.available_sort_by']['default'] = true; $result['use_config.default_sort_by']['default'] = true; $result['use_config.filter_price_range']['default'] = true; - if ($this->request->getParam('store') && $this->request->getParam('id')) { - $result['use_default.url_key']['checked'] = true; - $result['use_default.url_key']['default'] = true; - $result['use_default.url_key']['visible'] = true; - } else { - $result['use_default.url_key']['checked'] = false; - $result['use_default.url_key']['default'] = false; - $result['use_default.url_key']['visible'] = false; - } return $result; } @@ -431,7 +557,6 @@ protected function getFieldsMap() [ 'url_key', 'url_key_create_redirect', - 'use_default.url_key', 'url_key_group', 'meta_title', 'meta_keywords', @@ -461,4 +586,53 @@ protected function getFieldsMap() ], ]; } + + /** + * Retrieve scope overridden value + * + * @return ScopeOverriddenValue + * @deprecated + */ + private function getScopeOverriddenValue() + { + if (null === $this->scopeOverriddenValue) { + $this->scopeOverriddenValue = \Magento\Framework\App\ObjectManager::getInstance()->get( + ScopeOverriddenValue::class + ); + } + + return $this->scopeOverriddenValue; + } + + /** + * Retrieve array manager + * + * @return ArrayManager + * @deprecated + */ + private function getArrayManager() + { + if (null === $this->arrayManager) { + $this->arrayManager = \Magento\Framework\App\ObjectManager::getInstance()->get( + ArrayManager::class + ); + } + + return $this->arrayManager; + } + + /** + * Get FileInfo instance + * + * @return FileInfo + * + * @deprecated + */ + private function getFileInfo() + { + if ($this->fileInfo === null) { + $this->fileInfo = ObjectManager::getInstance()->get(FileInfo::class); + } + return $this->fileInfo; + } } diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php new file mode 100644 index 0000000000000..9a4c1f9f243b3 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -0,0 +1,107 @@ +filesystem = $filesystem; + $this->mime = $mime; + } + + /** + * Get WriteInterface instance + * + * @return WriteInterface + */ + private function getMediaDirectory() + { + if ($this->mediaDirectory === null) { + $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + } + return $this->mediaDirectory; + } + + /** + * Retrieve MIME type of requested file + * + * @param string $fileName + * @return string + */ + public function getMimeType($fileName) + { + $filePath = self::ENTITY_MEDIA_PATH . '/' . ltrim($fileName, '/'); + $absoluteFilePath = $this->getMediaDirectory()->getAbsolutePath($filePath); + + $result = $this->mime->getMimeType($absoluteFilePath); + return $result; + } + + /** + * Get file statistics data + * + * @param string $fileName + * @return array + */ + public function getStat($fileName) + { + $filePath = self::ENTITY_MEDIA_PATH . '/' . ltrim($fileName, '/'); + + $result = $this->getMediaDirectory()->stat($filePath); + return $result; + } + + /** + * Check if the file exists + * + * @param string $fileName + * @return bool + */ + public function isExist($fileName) + { + $filePath = self::ENTITY_MEDIA_PATH . '/' . ltrim($fileName, '/'); + + $result = $this->getMediaDirectory()->isExist($filePath); + return $result; + } +} diff --git a/app/code/Magento/Catalog/Model/Category/Link/ReadHandler.php b/app/code/Magento/Catalog/Model/Category/Link/ReadHandler.php index 6ac347cc1e4a2..4a76f0aea1595 100644 --- a/app/code/Magento/Catalog/Model/Category/Link/ReadHandler.php +++ b/app/code/Magento/Catalog/Model/Category/Link/ReadHandler.php @@ -1,6 +1,6 @@ _scopeConfig = $scopeConfig; $this->_configFactory = $configFactory; @@ -157,7 +160,14 @@ public function __construct( $this->_storeManager = $storeManager; $this->_eavConfig = $eavConfig; - parent::__construct($cache, $entityTypeFactory, $entityTypeCollectionFactory, $cacheState, $universalFactory); + parent::__construct( + $cache, + $entityTypeFactory, + $entityTypeCollectionFactory, + $cacheState, + $universalFactory, + $serializer + ); } /** diff --git a/app/code/Magento/Catalog/Model/Config/Backend/Category.php b/app/code/Magento/Catalog/Model/Config/Backend/Category.php index 4bb345b33d38e..2e1fcc2c42ac2 100644 --- a/app/code/Magento/Catalog/Model/Config/Backend/Category.php +++ b/app/code/Magento/Catalog/Model/Config/Backend/Category.php @@ -1,6 +1,6 @@ self::VALUE_FIXED, 'label' => __('Fixed')], + ['value' => self::VALUE_PERCENT, 'label' => __('Discount')], + ]; + } +} diff --git a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Type.php b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Type.php index 15c0941655d3b..423bbe5ca5781 100644 --- a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Type.php +++ b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Type.php @@ -1,6 +1,6 @@ objectFactory = $objectFactory; $this->productOptionFactory = $productOptionFactory; $this->extensionFactory = $extensionFactory; $this->customOptionFactory = $customOptionFactory; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); } /** @@ -99,7 +110,7 @@ public function processOptions(CartItemInterface $cartItem) protected function getOptions(CartItemInterface $cartItem) { $buyRequest = !empty($cartItem->getOptionByCode('info_buyRequest')) - ? unserialize($cartItem->getOptionByCode('info_buyRequest')->getValue()) + ? $this->serializer->unserialize($cartItem->getOptionByCode('info_buyRequest')->getValue()) : null; return is_array($buyRequest) && isset($buyRequest['options']) ? $buyRequest['options'] diff --git a/app/code/Magento/Catalog/Model/Design.php b/app/code/Magento/Catalog/Model/Design.php index ad5cc4ff42fe0..136f36a2a1398 100644 --- a/app/code/Magento/Catalog/Model/Design.php +++ b/app/code/Magento/Catalog/Model/Design.php @@ -1,6 +1,6 @@ resource = $resource; $this->connection = $resource->getConnection(); $this->storeManager = $storeManager; $this->config = $config; + $this->queryGenerator = $queryGenerator ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(QueryGenerator::class); } /** @@ -309,15 +320,26 @@ protected function isRangingNeeded() * @param int $range * @return \Magento\Framework\DB\Select[] */ - protected function prepareSelectsByRange(\Magento\Framework\DB\Select $select, $field, $range = self::RANGE_CATEGORY_STEP) - { - return $this->isRangingNeeded() ? $this->connection->selectsByRange( - $field, - $select, - $range - ) : [ - $select - ]; + protected function prepareSelectsByRange( + \Magento\Framework\DB\Select $select, + $field, + $range = self::RANGE_CATEGORY_STEP + ) { + if($this->isRangingNeeded()) { + $iterator = $this->queryGenerator->generate( + $field, + $select, + $range, + \Magento\Framework\DB\Query\BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR + ); + + $queries = []; + foreach ($iterator as $query) { + $queries[] = $query; + } + return $queries; + } + return [$select]; } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php index ba093c0129855..0f361ec64b1e0 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -1,6 +1,6 @@ _productIndexerHelper->getAttribute('status'); @@ -263,7 +267,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $select->joinLeft( $temporaryTableName, - "e.entity_id = " . $temporaryTableName . ".entity_id", + sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName), $columnsNames ); $allColumns = array_merge($allColumns, $columnsNames); @@ -277,7 +281,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS if (!empty($columnValueNames)) { $select->joinLeft( $temporaryValueTableName, - "e.${linkField} = " . $temporaryValueTableName . ".entity_id", + sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryValueTableName), $columnValueNames ); $allColumns = array_merge($allColumns, $columnValueNames); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/IndexerConfigData.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/IndexerConfigData.php index 0777e9a06e348..34bcfa2e484e5 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/IndexerConfigData.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/IndexerConfigData.php @@ -1,6 +1,6 @@ tableInstance = $connection->newTable($tableName); + } + + /** + * @inheritdoc + */ + public function addColumn($name, $type, $size = null, $options = [], $comment = null) + { + $this->tableInstance->addColumn($name, $type, $size, $options, $comment); + return $this; + } + + /** + * @inheritdoc + */ + public function getTable() + { + return $this->tableInstance; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php new file mode 100644 index 0000000000000..3651821ae10ac --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php @@ -0,0 +1,40 @@ +_connection->newTable($tableName); - $valueTemporaryTable = $this->_connection->newTable($valueTableName); + $temporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $tableName + ] + ); + $valueTemporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $valueTableName + ] + ); $flatColumns = $this->_productIndexerHelper->getFlatColumns(); - $temporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $temporaryTable->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); + $temporaryTableBuilder->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); - $temporaryTable->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $valueTemporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $valueTemporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ foreach ($columns as $columnName => $attribute) { @@ -145,7 +160,7 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField $column = $column[$attributeCode]; } - $temporaryTable->addColumn( + $temporaryTableBuilder->addColumn( $columnName, $column['type'], isset($column['length']) ? $column['length'] : null @@ -154,7 +169,7 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField $columnValueName = $attributeCode . $valueFieldSuffix; if (isset($flatColumns[$columnValueName])) { $columnValue = $flatColumns[$columnValueName]; - $valueTemporaryTable->addColumn( + $valueTemporaryTableBuilder->addColumn( $columnValueName, $columnValue['type'], isset($columnValue['length']) ? $columnValue['length'] : null @@ -162,11 +177,11 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField } } $this->_connection->dropTemporaryTable($tableName); - $this->_connection->createTemporaryTable($temporaryTable); + $this->_connection->createTemporaryTable($temporaryTableBuilder->getTable()); - if (count($valueTemporaryTable->getColumns()) > 1) { + if (count($valueTemporaryTableBuilder->getTable()->getColumns()) > 1) { $this->_connection->dropTemporaryTable($valueTableName); - $this->_connection->createTemporaryTable($valueTemporaryTable); + $this->_connection->createTemporaryTable($valueTemporaryTableBuilder->getTable()); $valueTables[$valueTableName] = $valueTableName; } } @@ -197,7 +212,8 @@ protected function _fillTemporaryEntityTable($tableName, array $columns, array $ if (!empty($columns)) { $select = $this->_connection->select(); $temporaryEntityTable = $this->_getTemporaryTableName($tableName); - $idsColumns = ['entity_id', 'type_id', 'attribute_set_id']; + $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $idsColumns = array_unique([$metadata->getLinkField(), 'entity_id', 'type_id', 'attribute_set_id']); $columns = array_merge($idsColumns, array_keys($columns)); @@ -261,7 +277,7 @@ protected function _fillTemporaryTable( ); $temporaryTableName = $this->_getTemporaryTableName($tableName); $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix; - $keyColumn = ['entity_id']; + $keyColumn = array_unique([$metadata->getLinkField(), 'entity_id']); $columns = array_merge($keyColumn, array_keys($columnsList)); $valueColumns = $keyColumn; $flatColumns = $this->_productIndexerHelper->getFlatColumns(); @@ -333,6 +349,19 @@ protected function _fillTemporaryTable( } } + /** + * @return BuilderInterfaceFactory + */ + private function getTableBuilderFactory() + { + if (null === $this->tableBuilderFactory) { + $this->tableBuilderFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(BuilderInterfaceFactory::class); + } + + return $this->tableBuilderFactory; + } + /** * @return \Magento\Framework\EntityManager\MetadataPool */ diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableData.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableData.php index a5a1b7d808f58..8f12fbfd358dc 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableData.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableData.php @@ -1,6 +1,6 @@ hasData('salable') && !$this->_catalogProduct->getSkipSaleableCheck()) { + return $this->getData('salable'); + } $this->_eventManager->dispatch('catalog_product_is_salable_before', ['product' => $this]); $salable = $this->isAvailable(); @@ -1626,6 +1628,7 @@ public function isSalable() 'catalog_product_is_salable_after', ['product' => $this, 'salable' => $object] ); + $this->setData('salable', $object->getIsSalable()); return $object->getIsSalable(); } @@ -2614,4 +2617,16 @@ private function getMediaGalleryProcessor() } return $this->mediaGalleryProcessor; } + + /** + * Set the associated products + * + * @param array $productIds + * @return $this + */ + public function setAssociatedProductIds(array $productIds) + { + $this->getExtensionAttributes()->setConfigurableProductLinks($productIds); + return $this; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Action.php b/app/code/Magento/Catalog/Model/Product/Action.php index 45194cbee0261..84502202981f7 100644 --- a/app/code/Magento/Catalog/Model/Product/Action.php +++ b/app/code/Magento/Catalog/Model/Product/Action.php @@ -1,6 +1,6 @@ _catalogProductType = $catalogProductType; $this->_groupManagement = $groupManagement; - parent::__construct($currencyFactory, $storeManager, $catalogData, $config, $localeFormat); + parent::__construct( + $currencyFactory, + $storeManager, + $catalogData, + $config, + $localeFormat, + $scopeOverriddenValue + ); } /** diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php index 450961d8f6289..3b9f46cfa143f 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php @@ -1,6 +1,6 @@ */ @@ -48,26 +49,33 @@ class Price extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend protected $localeFormat; /** - * Construct - * + * @var \Magento\Catalog\Model\Attribute\ScopeOverriddenValue + */ + private $scopeOverriddenValue; + + /** * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Helper\Data $catalogData * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Framework\Locale\FormatInterface $localeFormat + * @param ScopeOverriddenValue|null $scopeOverriddenValue */ public function __construct( \Magento\Directory\Model\CurrencyFactory $currencyFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Helper\Data $catalogData, \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Framework\Locale\FormatInterface $localeFormat + \Magento\Framework\Locale\FormatInterface $localeFormat, + ScopeOverriddenValue $scopeOverriddenValue = null ) { $this->_currencyFactory = $currencyFactory; $this->_storeManager = $storeManager; $this->_helper = $catalogData; $this->_config = $config; $this->localeFormat = $localeFormat; + $this->scopeOverriddenValue = $scopeOverriddenValue + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ScopeOverriddenValue::class); } /** @@ -102,43 +110,29 @@ public function setScope($attribute) } /** - * After Save Attribute manipulation + * After Save Price Attribute manipulation + * Processes product price attributes if price scoped to website and updates data when: + * * Price changed for non-default store view - will update price for all stores assigned to current website. + * * Price will be changed according to store currency even if price changed in product with default store id. + * * In a case when price was removed for non-default store (use default option checked) the default store price + * * will be used instead * * @param \Magento\Catalog\Model\Product $object * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function afterSave($object) { - $value = $object->getData($this->getAttribute()->getAttributeCode()); - /** - * Orig value is only for existing objects - */ - $oridData = $object->getOrigData(); - $origValueExist = $oridData && array_key_exists($this->getAttribute()->getAttributeCode(), $oridData); - if ($object->getStoreId() != 0 || !$value || $origValueExist) { - return $this; - } - - if ($this->getAttribute()->getIsGlobal() == ScopedAttributeInterface::SCOPE_WEBSITE) { - $baseCurrency = $this->_config->getValue( - \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, - 'default' - ); - - $storeIds = $object->getStoreIds(); - if (is_array($storeIds)) { - foreach ($storeIds as $storeId) { - $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); - if ($storeCurrency == $baseCurrency) { - continue; - } - $rate = $this->_currencyFactory->create()->load($baseCurrency)->getRate($storeCurrency); - if (!$rate) { - $rate = 1; - } - $newValue = $value * $rate; - $object->addAttributeUpdate($this->getAttribute()->getAttributeCode(), $newValue, $storeId); + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = $this->getAttribute(); + $attributeCode = $attribute->getAttributeCode(); + $value = $object->getData($attributeCode); + if ((float)$value > 0) { + if ($attribute->isScopeWebsite() && $object->getStoreId() != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + if ($this->isUseDefault($object)) { + $value = null; + } + foreach ((array)$object->getWebsiteStoreIds() as $storeId) { + $object->addAttributeUpdate($attributeCode, $value, $storeId); } } } @@ -146,6 +140,22 @@ public function afterSave($object) return $this; } + /** + * Check whether product uses default attribute's value in selected scope + * @param \Magento\Catalog\Model\Product $object + * @return bool + */ + private function isUseDefault($object) + { + $overridden = $this->scopeOverriddenValue->containsValue( + \Magento\Catalog\Api\Data\ProductInterface::class, + $object, + $this->getAttribute()->getAttributeCode(), + $object->getStoreId() + ); + return !$overridden; + } + /** * Validate * diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php index 327729cf83695..be98147abfc85 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php @@ -1,6 +1,6 @@ getAttribute(); $entity = $attribute->getEntity(); - $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); $attributeValue = $object->getData($attribute->getAttributeCode()); + $increment = null; while (!$entity->checkAttributeUniqueValue($attribute, $object)) { + if ($increment === null) { + $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); + } $sku = trim($attributeValue); if (strlen($sku . '-' . ++$increment) > self::SKU_MAX_LENGTH) { $sku = substr($sku, 0, -strlen($increment) - 1); diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php index 5c3fd4730aaed..b1b81512b53c4 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php @@ -1,6 +1,6 @@ getStockData() !== null || $stockData !== null) { + if ($object->getStockData() !== null && $stockData !== null) { $object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData)); } $object->unsetData($this->getAttribute()->getAttributeCode()); diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php index 480f8e8942e87..0153d2bfc5741 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php @@ -1,6 +1,6 @@ _productAttributeBackendTierprice = $productAttributeTierprice; parent::__construct( @@ -48,7 +52,8 @@ public function __construct( $config, $localeFormat, $catalogProductType, - $groupManagement + $groupManagement, + $scopeOverriddenValue ); } @@ -157,6 +162,7 @@ protected function modifyPriceData($object, $data) $data = parent::modifyPriceData($object, $data); foreach ($data as $key => $tierPrice) { if ($this->getPercentage($tierPrice)) { + $data[$key]['price'] = $object->getPrice() * (1 - $this->getPercentage($tierPrice) / 100); $data[$key]['website_price'] = $object->getPrice() * (1 - $this->getPercentage($tierPrice) / 100); } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Weight.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Weight.php index 3dfb0040fe6ac..495c62b429365 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Weight.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Weight.php @@ -1,6 +1,6 @@ - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Product\Attribute; diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Management.php b/app/code/Magento/Catalog/Model/Product/Attribute/Management.php index fa9b2ddee3cfe..4529b61147402 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Management.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Management.php @@ -1,7 +1,7 @@ - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Product\Attribute; diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php index e72d5b48a3691..b7d592263163a 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php @@ -1,7 +1,7 @@ setFrontendInput($existingModel->getFrontendInput()); if (is_array($attribute->getFrontendLabels())) { - $frontendLabel[0] = $existingModel->getDefaultFrontendLabel(); + $defaultFrontendLabel = $attribute->getDefaultFrontendLabel(); + $frontendLabel[0] = !empty($defaultFrontendLabel) + ? $defaultFrontendLabel + : $existingModel->getDefaultFrontendLabel(); foreach ($attribute->getFrontendLabels() as $item) { $frontendLabel[$item->getStoreId()] = $item->getLabel(); } $attribute->setDefaultFrontendLabel($frontendLabel); } - if (!$attribute->getIsUserDefined()) { - // Unset attribute field for system attributes - $attribute->setApplyTo(null); - } } else { $attribute->setAttributeId(null); @@ -178,13 +172,31 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib ); $attribute->setIsUserDefined(1); } - $this->attributeResource->save($attribute); - if (!empty($attribute->getData(AttributeInterface::OPTIONS))) { + $options = []; + $sortOrder = 0; + $default = []; + $optionIndex = 0; foreach ($attribute->getOptions() as $option) { - $this->getOptionManagement()->add($attribute->getAttributeCode(), $option); + $optionIndex++; + $optionId = $option->getValue() ?: 'option_' . $optionIndex; + $options['value'][$optionId][0] = $option->getLabel(); + $options['order'][$optionId] = $option->getSortOrder() ?: $sortOrder++; + if (is_array($option->getStoreLabels())) { + foreach ($option->getStoreLabels() as $label) { + $options['value'][$optionId][$label->getStoreId()] = $label->getLabel(); + } + } + if ($option->getIsDefault()) { + $default[] = $optionId; + } + } + $attribute->setDefault($default); + if (count($options)) { + $attribute->setOption($options); } } + $this->attributeResource->save($attribute); return $this->get($attribute->getAttributeCode()); } @@ -263,16 +275,4 @@ protected function validateFrontendInput($frontendInput) throw InputException::invalidFieldValue('frontend_input', $frontendInput); } } - - /** - * @return \Magento\Catalog\Api\ProductAttributeOptionManagementInterface - */ - private function getOptionManagement() - { - if (null === $this->optionManagement) { - $this->optionManagement = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Api\ProductAttributeOptionManagementInterface::class); - } - return $this->optionManagement; - } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/SetManagement.php b/app/code/Magento/Catalog/Model/Product/Attribute/SetManagement.php index d69e5a64939e9..e5ef94968d441 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/SetManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/SetManagement.php @@ -1,7 +1,7 @@ _storeManager->getStore()->getCode(); if ($cache = $this->_configCacheType->load($cacheKey)) { - $options = unserialize($cache); + $options = $this->getSerializer()->unserialize($cache); } else { /** @var \Magento\Directory\Model\Country $country */ $country = $this->_countryFactory->create(); /** @var \Magento\Directory\Model\ResourceModel\Country\Collection $collection */ $collection = $country->getResourceCollection(); $options = $collection->load()->toOptionArray(); - $this->_configCacheType->save(serialize($options), $cacheKey); + $this->_configCacheType->save($this->getSerializer()->serialize($options), $cacheKey); } return $options; } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Source/Inputtype.php b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Inputtype.php index f43d106248ede..be839be021e84 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Source/Inputtype.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Inputtype.php @@ -1,6 +1,6 @@ mediaConfig->getMediaAttributeCodes() as $mediaAttrCode) { + foreach ($this->getMediaAttributeCodes() as $mediaAttrCode) { $attrData = $product->getData($mediaAttrCode); - + if (empty($attrData) && empty($clearImages) && empty($newImages) && empty($existImages)) { + continue; + } if (in_array($attrData, $clearImages)) { $product->setData($mediaAttrCode, 'no_selection'); } @@ -160,12 +167,13 @@ public function execute($product, $arguments = []) if (in_array($attrData, array_keys($existImages))) { $product->setData($mediaAttrCode . '_label', $existImages[$attrData]['label']); } - - $product->addAttributeUpdate( - $mediaAttrCode, - $product->getData($mediaAttrCode), - $product->getStoreId() - ); + if (!empty($product->getData($mediaAttrCode))) { + $product->addAttributeUpdate( + $mediaAttrCode, + $product->getData($mediaAttrCode), + $product->getStoreId() + ); + } } $product->setData($attrCode, $value); @@ -393,4 +401,17 @@ protected function copyImage($file) ); } } + + /** + * Get Media Attribute Codes cached value + * + * @return array + */ + private function getMediaAttributeCodes() + { + if ($this->mediaAttributeCodes === null) { + $this->mediaAttributeCodes = $this->mediaConfig->getMediaAttributeCodes(); + } + return $this->mediaAttributeCodes; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php b/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php index cbe876561ad2e..8cfed2631994e 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php @@ -1,7 +1,7 @@ $existingEntry) { + $entryTypes = (array)$entry->getTypes(); + $existingEntryTypes = (array)$existingMediaGalleryEntries[$key]->getTypes(); + $existingMediaGalleryEntries[$key]->setTypes(array_diff($existingEntryTypes, $entryTypes)); + if ($existingEntry->getId() == $entry->getId()) { $found = true; if ($entry->getFile()) { $entry->setId(null); } $existingMediaGalleryEntries[$key] = $entry; - break; } } if (!$found) { diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/MimeTypeExtensionMap.php b/app/code/Magento/Catalog/Model/Product/Gallery/MimeTypeExtensionMap.php index e116e1b4f1cc8..4242539b9fe8b 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/MimeTypeExtensionMap.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/MimeTypeExtensionMap.php @@ -1,7 +1,7 @@ setData($attribute, null); + $product->setData($attribute, 'no_selection'); } } } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { - $product->setData($mediaAttribute, null); + $product->setData($mediaAttribute, 'no_selection'); } return $this; diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php index 28ecfff39c94f..1ed2ef20360e9 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php @@ -1,6 +1,6 @@ resourceModel->countImageUses($image['file']) > 1) { + $filesToDelete[] = ltrim($image['file'], '/'); + } } } } diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 7d8b464db3b34..03a3cdd044043 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -1,6 +1,6 @@ _coreFileStorageDatabase = $coreFileStorageDatabase; parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); - $result = $this->_mediaDirectory->create($this->_catalogProductMediaConfig->getBaseMediaPath()); $this->_imageFactory = $imageFactory; $this->_assetRepo = $assetRepo; $this->_viewFileSystem = $viewFileSystem; @@ -450,86 +464,29 @@ protected function _rgbToString($rgbArray) * @param string $file * @return $this * @throws \Exception - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function setBaseFile($file) { $this->_isBaseFilePlaceholder = false; - if ($file && 0 !== strpos($file, '/', 0)) { - $file = '/' . $file; - } - $baseDir = $this->_catalogProductMediaConfig->getBaseMediaPath(); - - if ('/no_selection' == $file) { - $file = null; - } - if ($file) { - if (!$this->_fileExists($baseDir . $file) || !$this->_checkMemory($baseDir . $file)) { - $file = null; - } - } - if (!$file) { + $this->imageAsset = $this->getViewAssetImageFactory()->create( + [ + 'miscParams' => $this->getMiscParams(), + 'filePath' => $file, + ] + ); + if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile()) + || !$this->_checkMemory($this->imageAsset->getSourceFile()) + ) { $this->_isBaseFilePlaceholder = true; - // check if placeholder defined in config - $isConfigPlaceholder = $this->_scopeConfig->getValue( - "catalog/placeholder/{$this->getDestinationSubdir()}_placeholder", - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + $this->imageAsset = $this->getViewAssetPlaceholderFactory()->create( + [ + 'type' => $this->getDestinationSubdir(), + ] ); - $configPlaceholder = '/placeholder/' . $isConfigPlaceholder; - if (!empty($isConfigPlaceholder) && $this->_fileExists($baseDir . $configPlaceholder)) { - $file = $configPlaceholder; - } else { - $this->_newFile = true; - return $this; - } - } - - $baseFile = $baseDir . $file; - - if (!$file || !$this->_mediaDirectory->isFile($baseFile)) { - throw new \Exception(__('We can\'t find the image file.')); } - $this->_baseFile = $baseFile; - - // build new filename (most important params) - $path = [ - $this->_catalogProductMediaConfig->getBaseMediaPath(), - 'cache', - $this->_storeManager->getStore()->getId(), - $path[] = $this->getDestinationSubdir(), - ]; - if (!empty($this->_width) || !empty($this->_height)) { - $path[] = "{$this->_width}x{$this->_height}"; - } - - // add misk params as a hash - $miscParams = [ - ($this->_keepAspectRatio ? '' : 'non') . 'proportional', - ($this->_keepFrame ? '' : 'no') . 'frame', - ($this->_keepTransparency ? '' : 'no') . 'transparency', - ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', - $this->_rgbToString($this->_backgroundColor), - 'angle' . $this->_angle, - 'quality' . $this->_quality, - ]; - - // if has watermark add watermark params to hash - if ($this->getWatermarkFile()) { - $miscParams[] = $this->getWatermarkFile(); - $miscParams[] = $this->getWatermarkImageOpacity(); - $miscParams[] = $this->getWatermarkPosition(); - $miscParams[] = $this->getWatermarkWidth(); - $miscParams[] = $this->getWatermarkHeight(); - } - - $path[] = md5(implode('_', $miscParams)); - - // append prepared filename - $this->_newFile = implode('/', $path) . $file; - // the $file contains heading slash + $this->_baseFile = $this->imageAsset->getSourceFile(); return $this; } @@ -543,6 +500,7 @@ public function getBaseFile() } /** + * @deprecated * @return bool|string */ public function getNewFile() @@ -691,10 +649,10 @@ public function setWatermark( */ public function saveFile() { - if ($this->_isBaseFilePlaceholder && $this->_newFile === true) { + if ($this->_isBaseFilePlaceholder) { return $this; } - $filename = $this->_mediaDirectory->getAbsolutePath($this->getNewFile()); + $filename = $this->getBaseFile() ? $this->imageAsset->getPath() : null; $this->getImageProcessor()->save($filename); $this->_coreFileStorageDatabase->saveFile($filename); return $this; @@ -705,17 +663,7 @@ public function saveFile() */ public function getUrl() { - if ($this->_newFile === true) { - $url = $this->_assetRepo->getUrl( - "Magento_Catalog::images/product/placeholder/{$this->getDestinationSubdir()}.jpg" - ); - } else { - $url = $this->_storeManager->getStore()->getBaseUrl( - \Magento\Framework\UrlInterface::URL_TYPE_MEDIA - ) . $this->_newFile; - } - - return $url; + return $this->imageAsset->getUrl(); } /** @@ -741,9 +689,7 @@ public function getDestinationSubdir() */ public function isCached() { - if (is_string($this->_newFile)) { - return $this->_fileExists($this->_newFile); - } + return file_exists($this->imageAsset->getPath()); } /** @@ -940,18 +886,72 @@ protected function _fileExists($filename) */ public function getResizedImageInfo() { - $fileInfo = null; - if ($this->_newFile === true) { - $asset = $this->_assetRepo->createAsset( - "Magento_Catalog::images/product/placeholder/{$this->getDestinationSubdir()}.jpg" - ); - $img = $asset->getSourceFile(); - $fileInfo = getimagesize($img); + if ($this->isBaseFilePlaceholder() == true) { + $image = $this->imageAsset->getSourceFile(); } else { - if ($this->_mediaDirectory->isFile($this->_mediaDirectory->getAbsolutePath($this->_newFile))) { - $fileInfo = getimagesize($this->_mediaDirectory->getAbsolutePath($this->_newFile)); - } + $image = $this->imageAsset->getPath(); } - return $fileInfo; + return getimagesize($image); + } + + /** + * @return \Magento\Catalog\Model\View\Asset\ImageFactory + */ + private function getViewAssetImageFactory() + { + if ($this->viewAssetImageFactory == null) { + $this->viewAssetImageFactory = ObjectManager::getInstance()->get( + \Magento\Catalog\Model\View\Asset\ImageFactory::class + ); + } + + return $this->viewAssetImageFactory; + } + + /** + * @return \Magento\Catalog\Model\View\Asset\PlaceholderFactory + */ + private function getViewAssetPlaceholderFactory() + { + if ($this->viewAssetPlaceholderFactory == null) { + $this->viewAssetPlaceholderFactory = ObjectManager::getInstance()->get( + \Magento\Catalog\Model\View\Asset\PlaceholderFactory::class + ); + } + + return $this->viewAssetPlaceholderFactory; + } + + /** + * Retrieve misc params based on all image attributes + * + * @return array + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function getMiscParams() + { + $miscParams = [ + 'image_type' => $this->getDestinationSubdir(), + 'image_height' => $this->getHeight(), + 'image_width' => $this->getWidth(), + 'keep_aspect_ratio' => ($this->_keepAspectRatio ? '' : 'non') . 'proportional', + 'keep_frame' => ($this->_keepFrame ? '' : 'no') . 'frame', + 'keep_transparency' => ($this->_keepTransparency ? '' : 'no') . 'transparency', + 'constrain_only' => ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', + 'background' => $this->_rgbToString($this->_backgroundColor), + 'angle' => $this->_angle, + 'quality' => $this->_quality, + ]; + + // if has watermark add watermark params to hash + if ($this->getWatermarkFile()) { + $miscParams['watermark_file'] = $this->getWatermarkFile(); + $miscParams['watermark_image_opacity'] = $this->getWatermarkImageOpacity(); + $miscParams['watermark_position'] = $this->getWatermarkPosition(); + $miscParams['watermark_width'] = $this->getWatermarkWidth(); + $miscParams['watermark_height'] = $this->getWatermarkHeight(); + } + + return $miscParams; } } diff --git a/app/code/Magento/Catalog/Model/Product/Image/Cache.php b/app/code/Magento/Catalog/Model/Product/Image/Cache.php index 830e8be2f3852..2a5316583ff6e 100644 --- a/app/code/Magento/Catalog/Model/Product/Image/Cache.php +++ b/app/code/Magento/Catalog/Model/Product/Image/Cache.php @@ -1,6 +1,6 @@ metadataPool = $metadataPool; $this->linkResource = $linkResource; $this->productLinkRepository = $productLinkRepository; @@ -54,12 +53,18 @@ public function __construct( */ public function execute($entityType, $entity) { - /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ - foreach ($this->productLinkRepository->getList($entity) as $link) { - $this->productLinkRepository->delete($link); + $link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField()); + if ($this->linkResource->hasProductLinks($link)) { + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ + foreach ($this->productLinkRepository->getList($entity) as $link) { + $this->productLinkRepository->delete($link); + } } - foreach ($entity->getProductLinks() as $link) { - $this->productLinkRepository->save($link); + $productLinks = $entity->getProductLinks(); + if (count($productLinks) > 0) { + foreach ($entity->getProductLinks() as $link) { + $this->productLinkRepository->save($link); + } } return $entity; } diff --git a/app/code/Magento/Catalog/Model/Product/LinkTypeProvider.php b/app/code/Magento/Catalog/Model/Product/LinkTypeProvider.php index 8a878e68957dc..e9f3e490bec57 100644 --- a/app/code/Magento/Catalog/Model/Product/LinkTypeProvider.php +++ b/app/code/Magento/Catalog/Model/Product/LinkTypeProvider.php @@ -2,7 +2,7 @@ /** * Collection of the available product link types * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Product; diff --git a/app/code/Magento/Catalog/Model/Product/Media/AttributeManagement.php b/app/code/Magento/Catalog/Model/Product/Media/AttributeManagement.php index 1bcc025fa2e7c..22f7690c8fc10 100644 --- a/app/code/Magento/Catalog/Model/Product/Media/AttributeManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Media/AttributeManagement.php @@ -1,6 +1,6 @@ productRepository->get($productSku); $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $option->setData('product_id', $product->getData($metadata->getLinkField())); - $option->setOptionId(null); + $option->setData('store_id', $product->getStoreId()); + + if ($option->getOptionId()) { + $options = $product->getOptions(); + if (!$options) { + $options = $this->getProductOptions($product); + } + + $persistedOption = array_filter($options, function ($iOption) use ($option) { + return $option->getOptionId() == $iOption->getOptionId(); + }); + $persistedOption = reset($persistedOption); + + if (!$persistedOption) { + throw new NoSuchEntityException(); + } + $originalValues = $persistedOption->getValues(); + $newValues = $option->getData('values'); + if ($newValues) { + $newValues = $this->markRemovedValues($newValues, $originalValues); + $option->setData('values', $newValues); + } + } $option->save(); return $option; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index 7c32232b6591a..72241cbe6e701 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -1,6 +1,6 @@ getOptions(); + $optionIds = []; + + if ($options) { + $optionIds = array_map(function ($option) { + /** @var \Magento\Catalog\Model\Product\Option $option */ + return $option->getOptionId(); + }, $options); + } + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity */ foreach ($this->optionRepository->getProductOptions($entity) as $option) { - $this->optionRepository->delete($option); + if (!in_array($option->getOptionId(), $optionIds)) { + $this->optionRepository->delete($option); + } } - if ($entity->getOptions()) { - foreach ($entity->getOptions() as $option) { + if ($options) { + foreach ($options as $option) { $this->optionRepository->save($option); } } + return $entity; } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type.php b/app/code/Magento/Catalog/Model/Product/Option/Type.php index 4b59ca40dfd79..893b91021b818 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type.php @@ -1,6 +1,6 @@ _localeDate = $localeDate; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); parent::__construct($checkoutSession, $scopeConfig, $data); } @@ -269,7 +280,7 @@ public function prepareOptionValueForRequest($optionValue) $confItem = $this->getConfigurationItem(); $infoBuyRequest = $confItem->getOptionByCode('info_buyRequest'); try { - $value = unserialize($infoBuyRequest->getValue()); + $value = $this->serializer->unserialize($infoBuyRequest->getValue()); if (is_array($value) && isset($value['options']) && isset($value['options'][$this->getOption()->getId()]) ) { return $value['options'][$this->getOption()->getId()]; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php index 9c54207a3645d..b54266e17b402 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php @@ -1,6 +1,6 @@ _itemOptionFactory = $itemOptionFactory; $this->_urlBuilder = $urlBuilder; $this->_escaper = $escaper; $this->_coreFileStorageDatabase = $coreFileStorageDatabase; + $this->filesystem = $filesystem ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Filesystem::class); + $this->_rootDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); $this->validatorInfo = $validatorInfo; $this->validatorFile = $validatorFile; + $this->serializer = $serializer ? $serializer : ObjectManager::getInstance()->get(Json::class); parent::__construct($checkoutSession, $scopeConfig, $data); } @@ -265,7 +285,7 @@ public function prepareForCart() // Save option in request, because we have no $_FILES['options'] $requestOptions[$this->getOption()->getId()] = $value; - $result = serialize($value); + $result = $this->serializer->serialize($value); } else { /* * Clear option info from request, so it won't be stored in our db upon @@ -296,7 +316,7 @@ public function getFormattedOptionValue($optionValue) { if ($this->_formattedOptionValue === null) { try { - $value = unserialize($optionValue); + $value = $this->serializer->unserialize($optionValue); $customOptionUrlParams = $this->getCustomOptionUrlParams() ? $this->getCustomOptionUrlParams() : [ 'id' => $this->getConfigurationItemOption()->getId(), @@ -306,7 +326,7 @@ public function getFormattedOptionValue($optionValue) $value['url'] = ['route' => $this->_customOptionDownloadUrl, 'params' => $customOptionUrlParams]; $this->_formattedOptionValue = $this->_getOptionHtml($value); - $this->getConfigurationItemOption()->setValue(serialize($value)); + $this->getConfigurationItemOption()->setValue($this->serializer->serialize($value)); return $this->_formattedOptionValue; } catch (\Exception $e) { return $optionValue; @@ -354,7 +374,7 @@ protected function _unserializeValue($value) if (is_array($value)) { return $value; } elseif (is_string($value) && !empty($value)) { - return unserialize($value); + return $this->serializer->unserialize($value); } else { return []; } @@ -376,11 +396,13 @@ public function getPrintableOptionValue($optionValue) * * @param string $optionValue Prepared for cart option value * @return string + * + * @deprecated */ public function getEditableOptionValue($optionValue) { try { - $value = unserialize($optionValue); + $value = $this->serializer->unserialize($optionValue); return sprintf( '%s [%d]', $this->_escaper->escapeHtml($value['title']), @@ -399,6 +421,8 @@ public function getEditableOptionValue($optionValue) * @return string|null * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @deprecated */ public function parseOptionValue($optionValue, $productOptionValues) { @@ -407,7 +431,7 @@ public function parseOptionValue($optionValue, $productOptionValues) $confItemOptionId = $matches[1]; $option = $this->_itemOptionFactory->create()->load($confItemOptionId); try { - unserialize($option->getValue()); + $this->serializer->unserialize($option->getValue()); return $option->getValue(); } catch (\Exception $e) { return null; @@ -426,7 +450,7 @@ public function parseOptionValue($optionValue, $productOptionValues) public function prepareOptionValueForRequest($optionValue) { try { - $result = unserialize($optionValue); + $result = $this->serializer->unserialize($optionValue); return $result; } catch (\Exception $e) { return null; @@ -442,7 +466,7 @@ public function copyQuoteToOrder() { $quoteOption = $this->getConfigurationItemOption(); try { - $value = unserialize($quoteOption->getValue()); + $value = $this->serializer->unserialize($quoteOption->getValue()); if (!isset($value['quote_path'])) { throw new \Exception(); } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php index 71011fe55072d..8d12349af0354 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php @@ -1,6 +1,6 @@ getProduct()->getStoreId(); } foreach ($values as $value) { + if (isset($value['is_delete']) && (bool)$value['is_delete']) { + continue; + } $type = isset($value['price_type']) ? $value['price_type'] : null; $price = isset($value['price']) ? $value['price'] : null; $title = isset($value['title']) ? $value['title'] : null; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Text.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Text.php index 84d2439097185..ebc7a6ba71e59 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Text.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Text.php @@ -1,6 +1,6 @@ getOption()->getStoreId() ); - $this->unsetData('option_type_id'); + if ($this->getData('is_delete') == '1') { if ($this->getId()) { $this->deleteValues($this->getId()); diff --git a/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php b/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php new file mode 100644 index 0000000000000..0b3976b3857fc --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php @@ -0,0 +1,79 @@ +setData(self::PRICE, $price); + } + + /** + * {@inheritdoc} + */ + public function getPrice() + { + return $this->getData(self::PRICE); + } + + /** + * {@inheritdoc} + */ + public function setStoreId($storeId) + { + return $this->setData(self::STORE_ID, $storeId); + } + + /** + * {@inheritdoc} + */ + public function getStoreId() + { + return $this->getData(self::STORE_ID); + } + + /** + * {@inheritdoc} + */ + public function setSku($sku) + { + return $this->setData(self::SKU, $sku); + } + + /** + * {@inheritdoc} + */ + public function getSku() + { + return $this->getData(self::SKU); + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes(\Magento\Catalog\Api\Data\BasePriceExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php new file mode 100644 index 0000000000000..097e1a13dc6f9 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php @@ -0,0 +1,228 @@ +pricePersistenceFactory = $pricePersistenceFactory; + $this->basePriceInterfaceFactory = $basePriceInterfaceFactory; + $this->productIdLocator = $productIdLocator; + $this->storeRepository = $storeRepository; + $this->productRepository = $productRepository; + $this->validationResult = $validationResult; + $this->allowedProductTypes = $allowedProductTypes; + $this->invalidSkuProcessor = $invalidSkuProcessor; + } + + /** + * {@inheritdoc} + */ + public function get(array $skus) + { + $skus = $this->invalidSkuProcessor->filterSkuList( + $skus, + $this->allowedProductTypes, + $this->priceTypeAllowed + ); + $rawPrices = $this->getPricePersistence()->get($skus); + $prices = []; + foreach ($rawPrices as $rawPrice) { + $price = $this->basePriceInterfaceFactory->create(); + $sku = $this->getPricePersistence() + ->retrieveSkuById($rawPrice[$this->getPricePersistence()->getEntityLinkField()], $skus); + $price->setSku($sku); + $price->setPrice($rawPrice['value']); + $price->setStoreId($rawPrice['store_id']); + $prices[] = $price; + } + + return $prices; + } + + /** + * {@inheritdoc} + */ + public function update(array $prices) + { + $prices = $this->retrieveValidPrices($prices); + $formattedPrices = []; + + foreach ($prices as $price) { + $ids = array_keys($this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()])[$price->getSku()]); + foreach ($ids as $id) { + $formattedPrices[] = [ + 'store_id' => $price->getStoreId(), + $this->getPricePersistence()->getEntityLinkField() => $id, + 'value' => $price->getPrice(), + ]; + } + } + + $this->getPricePersistence()->update($formattedPrices); + + return $this->validationResult->getFailedItems(); + } + + /** + * Get price persistence. + * + * @return PricePersistence + */ + private function getPricePersistence() + { + if (!$this->pricePersistence) { + $this->pricePersistence = $this->pricePersistenceFactory->create(['attributeCode' => $this->attributeCode]); + } + + return $this->pricePersistence; + } + + /** + * Retrieve valid prices that do not contain any errors. + * + * @param \Magento\Catalog\Api\Data\BasePriceInterface[] $prices + * @return array + */ + private function retrieveValidPrices(array $prices) + { + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $invalidSkus = $this->invalidSkuProcessor->retrieveInvalidSkuList( + $skus, + $this->allowedProductTypes, + $this->priceTypeAllowed + ); + + foreach ($prices as $id => $price) { + if (!$price->getSku() || in_array($price->getSku(), $invalidSkus)) { + $this->validationResult->addFailedItem( + $id, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'SKU', 'fieldValue' => $price->getSku()] + ); + } + if (null === $price->getPrice() || $price->getPrice() < 0) { + $this->validationResult->addFailedItem( + $id, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'Price', 'fieldValue' => $price->getPrice()] + ); + } + try { + $this->storeRepository->getById($price->getStoreId()); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->validationResult->addFailedItem( + $id, + __( + 'Requested store is not found. Row ID: SKU = %SKU, Store ID: %storeId.', + ['SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ), + ['SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ); + } + } + + foreach ($this->validationResult->getFailedRowIds() as $id) { + unset($prices[$id]); + } + + return $prices; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/Cost.php b/app/code/Magento/Catalog/Model/Product/Price/Cost.php new file mode 100644 index 0000000000000..f3991bf188187 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/Cost.php @@ -0,0 +1,79 @@ +setData(self::COST, $cost); + } + + /** + * {@inheritdoc} + */ + public function getCost() + { + return $this->getData(self::COST); + } + + /** + * {@inheritdoc} + */ + public function setStoreId($storeId) + { + return $this->setData(self::STORE_ID, $storeId); + } + + /** + * {@inheritdoc} + */ + public function getStoreId() + { + return $this->getData(self::STORE_ID); + } + + /** + * {@inheritdoc} + */ + public function setSku($sku) + { + return $this->setData(self::SKU, $sku); + } + + /** + * {@inheritdoc} + */ + public function getSku() + { + return $this->getData(self::SKU); + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes(\Magento\Catalog\Api\Data\CostExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php b/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php new file mode 100644 index 0000000000000..977d1948bbf0b --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php @@ -0,0 +1,219 @@ +pricePersistenceFactory = $pricePersistenceFactory; + $this->costInterfaceFactory = $costInterfaceFactory; + $this->productIdLocator = $productIdLocator; + $this->storeRepository = $storeRepository; + $this->validationResult = $validationResult; + $this->invalidSkuProcessor = $invalidSkuProcessor; + $this->allowedProductTypes = $allowedProductTypes; + } + + /** + * {@inheritdoc} + */ + public function get(array $skus) + { + $skus = $this->invalidSkuProcessor->filterSkuList($skus, $this->allowedProductTypes); + $rawPrices = $this->getPricePersistence()->get($skus); + $prices = []; + foreach ($rawPrices as $rawPrice) { + $price = $this->costInterfaceFactory->create(); + $sku = $this->getPricePersistence() + ->retrieveSkuById($rawPrice[$this->getPricePersistence()->getEntityLinkField()], $skus); + $price->setSku($sku); + $price->setCost($rawPrice['value']); + $price->setStoreId($rawPrice['store_id']); + $prices[] = $price; + } + + return $prices; + } + + /** + * {@inheritdoc} + */ + public function update(array $prices) + { + $prices = $this->retrieveValidPrices($prices); + $formattedPrices = []; + + foreach ($prices as $price) { + $productIdsBySkus = $this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()]); + $productIds = array_keys($productIdsBySkus[$price->getSku()]); + foreach ($productIds as $id) { + $formattedPrices[] = [ + 'store_id' => $price->getStoreId(), + $this->getPricePersistence()->getEntityLinkField() => $id, + 'value' => $price->getCost(), + ]; + } + } + + $this->getPricePersistence()->update($formattedPrices); + + return $this->validationResult->getFailedItems(); + } + + /** + * {@inheritdoc} + */ + public function delete(array $skus) + { + $skus = $this->invalidSkuProcessor->filterSkuList($skus, $this->allowedProductTypes); + $this->getPricePersistence()->delete($skus); + + return true; + } + + /** + * Get price persistence. + * + * @return PricePersistence + */ + private function getPricePersistence() + { + if (!$this->pricePersistence) { + $this->pricePersistence = $this->pricePersistenceFactory->create(['attributeCode' => $this->attributeCode]); + } + + return $this->pricePersistence; + } + + /** + * Retrieve valid prices that do not contain any errors. + * + * @param array $prices + * @return array + */ + private function retrieveValidPrices(array $prices) + { + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $invalidSkus = $this->invalidSkuProcessor->retrieveInvalidSkuList($skus, $this->allowedProductTypes); + + foreach ($prices as $id => $price) { + if (!$price->getSku() || in_array($price->getSku(), $invalidSkus)) { + $this->validationResult->addFailedItem( + $id, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'SKU', 'fieldValue' => $price->getSku()] + ); + } + if (null === $price->getCost() || $price->getCost() < 0) { + $this->validationResult->addFailedItem( + $id, + __( + 'Invalid attribute Cost = %cost. Row ID: SKU = %SKU, Store ID: %storeId.', + ['cost' => $price->getCost(), 'SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ), + ['cost' => $price->getCost(), 'SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ); + } + try { + $this->storeRepository->getById($price->getStoreId()); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->validationResult->addFailedItem( + $id, + __( + 'Requested store is not found. Row ID: SKU = %SKU, Store ID: %storeId.', + ['SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ), + ['SKU' => $price->getSku(), 'storeId' => $price->getStoreId()] + ); + } + } + + foreach ($this->validationResult->getFailedRowIds() as $id) { + unset($prices[$id]); + } + + return $prices; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php new file mode 100644 index 0000000000000..87effd3a58aba --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php @@ -0,0 +1,228 @@ +attributeResource = $attributeResource; + $this->attributeRepository = $attributeRepository; + $this->attributeCode = $attributeCode; + $this->productIdLocator = $productIdLocator; + $this->metadataPool = $metadataPool; + } + + /** + * Get prices by SKUs. + * + * @param array $skus + * @return array + */ + public function get(array $skus) + { + $ids = $this->retrieveAffectedIds($skus); + $select = $this->attributeResource->getConnection() + ->select() + ->from($this->attributeResource->getTable($this->table)); + return $this->attributeResource->getConnection()->fetchAll( + $select->where($this->getEntityLinkField() . ' IN (?)', $ids) + ->where('attribute_id = ?', $this->getAttributeId()) + ); + } + + /** + * Update prices. + * + * @param array $prices + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function update(array $prices) + { + array_walk($prices, function (&$price) { + return $price['attribute_id'] = $this->getAttributeId(); + }); + $connection = $this->attributeResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) { + $this->attributeResource->getConnection()->insertOnDuplicate( + $this->attributeResource->getTable($this->table), + $pricesBunch, + ['value'] + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotSaveException( + __('Could not save Prices.'), + $e + ); + } + } + + /** + * Delete product attribute by SKU. + * + * @param array $skus + * @return void + * @throws \Magento\Framework\Exception\CouldNotDeleteException + */ + public function delete(array $skus) + { + $ids = $this->retrieveAffectedIds($skus); + $connection = $this->attributeResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) { + $this->attributeResource->getConnection()->delete( + $this->attributeResource->getTable($this->table), + [ + 'attribute_id = ?' => $this->getAttributeId(), + $this->getEntityLinkField() . ' IN (?)' => $idsBunch + ] + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotDeleteException( + __('Could not delete Prices'), + $e + ); + } + } + + /** + * Retrieve SKU by product ID. + * + * @param int $id + * @param array $skus + * @return int|null + */ + public function retrieveSkuById($id, $skus) + { + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) { + if (false !== array_key_exists($id, $ids)) { + return $sku; + } + } + + return null; + } + + /** + * Get attribute ID. + * + * @return int + */ + private function getAttributeId() + { + if (!$this->attributeId) { + $this->attributeId = $this->attributeRepository->get($this->attributeCode)->getAttributeId(); + } + + return $this->attributeId; + } + + /** + * Retrieve affected product IDs. + * + * @param array $skus + * @return array + */ + private function retrieveAffectedIds(array $skus) + { + $affectedIds = []; + + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) { + $affectedIds = array_merge($affectedIds, array_keys($productIds)); + } + + return array_unique($affectedIds); + } + + /** + * Get link field. + * + * @return string + */ + public function getEntityLinkField() + { + return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/PriceUpdateResult.php b/app/code/Magento/Catalog/Model/Product/Price/PriceUpdateResult.php new file mode 100644 index 0000000000000..6922879d0fa93 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/PriceUpdateResult.php @@ -0,0 +1,64 @@ +getData(self::MESSAGE); + } + + /** + * {@inheritdoc} + */ + public function setMessage($message) + { + return $this->setData(self::MESSAGE, $message); + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->getData(self::PARAMETERS); + } + + /** + * {@inheritdoc} + */ + public function setParameters(array $parameters) + { + return $this->setData(self::PARAMETERS, $parameters); + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes( + \Magento\Catalog\Api\Data\PriceUpdateResultExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/SpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Price/SpecialPrice.php new file mode 100644 index 0000000000000..e80f5ad29657f --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/SpecialPrice.php @@ -0,0 +1,112 @@ +setData(self::PRICE, $price); + } + + /** + * {@inheritdoc} + */ + public function getPrice() + { + return $this->getData(self::PRICE); + } + + /** + * {@inheritdoc} + */ + public function setStoreId($storeId) + { + return $this->setData(self::STORE_ID, $storeId); + } + + /** + * {@inheritdoc} + */ + public function getStoreId() + { + return $this->getData(self::STORE_ID); + } + + /** + * {@inheritdoc} + */ + public function setSku($sku) + { + return $this->setData(self::SKU, $sku); + } + + /** + * {@inheritdoc} + */ + public function getSku() + { + return $this->getData(self::SKU); + } + + /** + * {@inheritdoc} + */ + public function setPriceFrom($datetime) + { + return $this->setData(self::PRICE_FROM, $datetime); + } + + /** + * {@inheritdoc} + */ + public function getPriceFrom() + { + return $this->getData(self::PRICE_FROM); + } + + /** + * {@inheritdoc} + */ + public function setPriceTo($datetime) + { + return $this->setData(self::PRICE_TO, $datetime); + } + + /** + * {@inheritdoc} + */ + public function getPriceTo() + { + return $this->getData(self::PRICE_TO); + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes( + \Magento\Catalog\Api\Data\SpecialPriceExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php new file mode 100644 index 0000000000000..a039aa93a61de --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php @@ -0,0 +1,298 @@ +specialPriceResource = $specialPriceResource; + $this->specialPriceFactory = $specialPriceFactory; + $this->productIdLocator = $productIdLocator; + $this->storeRepository = $storeRepository; + $this->validationResult = $validationResult; + $this->invalidSkuProcessor = $invalidSkuProcessor; + $this->allowedProductTypes = $allowedProductTypes; + } + + /** + * {@inheritdoc} + */ + public function get(array $skus) + { + $skus = $this->invalidSkuProcessor->filterSkuList($skus, $this->allowedProductTypes); + $rawPrices = $this->specialPriceResource->get($skus); + + $prices = []; + foreach ($rawPrices as $rawPrice) { + /** @var \Magento\Catalog\Api\Data\SpecialPriceInterface $price */ + $price = $this->specialPriceFactory->create(); + $sku = isset($rawPrice['sku']) + ? $rawPrice['sku'] + : $this->retrieveSkuById($rawPrice[$this->specialPriceResource->getEntityLinkField()], $skus); + $price->setSku($sku); + $price->setPrice($rawPrice['value']); + $price->setStoreId($rawPrice['store_id']); + $price->setPriceFrom($rawPrice['price_from']); + $price->setPriceTo($rawPrice['price_to']); + $prices[] = $price; + } + + return $prices; + } + + /** + * {@inheritdoc} + */ + public function update(array $prices) + { + $prices = $this->retrieveValidPrices($prices); + $this->specialPriceResource->update($prices); + + return $this->validationResult->getFailedItems(); + } + + /** + * {@inheritdoc} + */ + public function delete(array $prices) + { + $prices = $this->retrieveValidPrices($prices); + $this->specialPriceResource->delete($prices); + + return $this->validationResult->getFailedItems(); + } + + /** + * Retrieve prices with correct values. + * + * @param array $prices + * @return array + */ + private function retrieveValidPrices(array $prices) + { + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $failedSkus = $this->invalidSkuProcessor->retrieveInvalidSkuList($skus, $this->allowedProductTypes); + + foreach ($prices as $key => $price) { + if (!$price->getSku() || in_array($price->getSku(), $failedSkus)) { + $this->validationResult->addFailedItem( + $key, + __( + 'Requested product doesn\'t exist. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ), + [ + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ); + } + $this->checkPrice($price, $key); + $this->checkDate($price, $price->getPriceFrom(), 'Price From', $key); + $this->checkDate($price, $price->getPriceTo(), 'Price To', $key); + try { + $this->storeRepository->getById($price->getStoreId()); + } catch (NoSuchEntityException $e) { + $this->validationResult->addFailedItem( + $key, + __( + 'Requested store is not found. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ), + [ + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ); + } + } + + foreach ($this->validationResult->getFailedRowIds() as $id) { + unset($prices[$id]); + } + + return $prices; + } + + /** + * Check that date value is correct and add error to aggregator if it contains incorrect data. + * + * @param \Magento\Catalog\Api\Data\SpecialPriceInterface $price + * @param string $value + * @param string $label + * @param int $key + * @return void + */ + private function checkDate(\Magento\Catalog\Api\Data\SpecialPriceInterface $price, $value, $label, $key) + { + if ($value && !$this->isCorrectDateValue($value)) { + $this->validationResult->addFailedItem( + $key, + __( + 'Invalid attribute %label = %priceTo. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'label' => $label, + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ), + [ + 'label' => $label, + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ); + } + } + + /** + * Check that provided price value is not empty and not lower then zero and add error to aggregator if price + * contains not valid data. + * + * @param \Magento\Catalog\Api\Data\SpecialPriceInterface $price + * @param int $key + * @return void + */ + private function checkPrice(\Magento\Catalog\Api\Data\SpecialPriceInterface $price, $key) + { + if (null === $price->getPrice() || $price->getPrice() < 0) { + $this->validationResult->addFailedItem( + $key, + __( + 'Invalid attribute Price = %price. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'price' => $price->getPrice(), + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ), + [ + 'price' => $price->getPrice(), + 'SKU' => $price->getSku(), + 'storeId' => $price->getStoreId(), + 'priceFrom' => $price->getPriceFrom(), + 'priceTo' => $price->getPriceTo() + ] + ); + } + } + + /** + * Retrieve SKU by product ID. + * + * @param int $productId + * @param array $skus + * @return string|null + */ + private function retrieveSkuById($productId, array $skus) + { + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) { + if (isset($ids[$productId])) { + return $sku; + } + } + + return null; + } + + /** + * Check that date value is correct. + * + * @param string $date + * @return bool + */ + private function isCorrectDateValue($date) + { + $actualDate = date('Y-m-d H:i:s', strtotime($date)); + return $actualDate && $actualDate === $date; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php b/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php new file mode 100644 index 0000000000000..0b61ae4bcbf0c --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php @@ -0,0 +1,127 @@ +setData(self::PRICE, $price); + } + + /** + * {@inheritdoc} + */ + public function getPrice() + { + return $this->getData(self::PRICE); + } + + /** + * {@inheritdoc} + */ + public function setPriceType($type) + { + return $this->setData(self::PRICE_TYPE, $type); + } + + /** + * {@inheritdoc} + */ + public function getPriceType() + { + return $this->getData(self::PRICE_TYPE); + } + + /** + * {@inheritdoc} + */ + public function setWebsiteId($websiteId) + { + return $this->setData(self::WEBSITE_ID, $websiteId); + } + + /** + * {@inheritdoc} + */ + public function getWebsiteId() + { + return $this->getData(self::WEBSITE_ID); + } + + /** + * {@inheritdoc} + */ + public function setSku($sku) + { + return $this->setData(self::SKU, $sku); + } + + /** + * {@inheritdoc} + */ + public function getSku() + { + return $this->getData(self::SKU); + } + + /** + * {@inheritdoc} + */ + public function setCustomerGroup($group) + { + return $this->setData(self::CUSTOMER_GROUP, $group); + } + + /** + * {@inheritdoc} + */ + public function getCustomerGroup() + { + return $this->getData(self::CUSTOMER_GROUP); + } + + /** + * {@inheritdoc} + */ + public function setQuantity($quantity) + { + return $this->setData(self::QUANTITY, $quantity); + } + + /** + * {@inheritdoc} + */ + public function getQuantity() + { + return $this->getData(self::QUANTITY); + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes(\Magento\Catalog\Api\Data\TierPriceExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php new file mode 100644 index 0000000000000..1cc608f662570 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php @@ -0,0 +1,169 @@ +tierPriceFactory = $tierPriceFactory; + $this->tierPricePersistence = $tierPricePersistence; + $this->customerGroupRepository = $customerGroupRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->filterBuilder = $filterBuilder; + } + + /** + * Create populated tier price DTO. + * + * @param array $rawPrice + * @param string $sku + * @return \Magento\Catalog\Api\Data\TierPriceInterface + */ + public function create(array $rawPrice, $sku) + { + $price = $this->tierPriceFactory->create(); + $price->setPrice(isset($rawPrice['percentage_value']) ? $rawPrice['percentage_value'] : $rawPrice['value']); + $price->setPriceType( + isset($rawPrice['percentage_value']) + ? TierPriceInterface::PRICE_TYPE_DISCOUNT + : TierPriceInterface::PRICE_TYPE_FIXED + ); + $price->setWebsiteId($rawPrice['website_id']); + $price->setSku($sku); + $price->setCustomerGroup( + $rawPrice['all_groups'] == $this->allGroupsId + ? $this->allGroupsValue + : $this->customerGroupRepository->getById($rawPrice['customer_group_id'])->getCode() + ); + $price->setQuantity($rawPrice['qty']); + + return $price; + } + + /** + * Build tier price skeleton that has DB consistent format. + * + * @param TierPriceInterface $price + * @param int $id + * @return array + */ + public function createSkeleton(TierPriceInterface $price, $id) + { + return [ + $this->tierPricePersistence->getEntityLinkField() => $id, + 'all_groups' => $this->retrievePriceForAllGroupsValue($price), + 'customer_group_id' => $this->retrievePriceForAllGroupsValue($price) === $this->allGroupsId + ? 0 + : $this->retrieveGroupValue(strtolower($price->getCustomerGroup())), + 'qty' => $price->getQuantity(), + 'value' => $price->getPriceType() === TierPriceInterface::PRICE_TYPE_FIXED + ? $price->getPrice() + : 0.00, + 'percentage_value' => $price->getPriceType() === TierPriceInterface::PRICE_TYPE_DISCOUNT + ? $price->getPrice() + : null, + 'website_id' => $price->getWebsiteId() + ]; + } + + /** + * Retrieve price for all groups value. + * + * @param TierPriceInterface $price + * @return int + */ + private function retrievePriceForAllGroupsValue(TierPriceInterface $price) + { + return strcasecmp($price->getCustomerGroup(), $this->allGroupsValue) === 0 ? $this->allGroupsId : 0; + } + + /** + * Retrieve customer group id by code. + * + * @param string $code + * @return int + * @throws NoSuchEntityException + */ + private function retrieveGroupValue($code) + { + if (!isset($this->customerGroupsByCode[$code])) { + $searchCriteria = $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder->setField('customer_group_code')->setValue($code)->create() + ] + ); + $items = $this->customerGroupRepository->getList($searchCriteria->create())->getItems(); + $item = array_shift($items); + $this->customerGroupsByCode[strtolower($item->getCode())] = $item->getId(); + } + + return $this->customerGroupsByCode[$code]; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php new file mode 100644 index 0000000000000..a7149a60c41a7 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php @@ -0,0 +1,166 @@ +tierpriceResource = $tierpriceResource; + $this->metadataPool = $metadataPool; + } + + /** + * Get tier prices by product IDs. + * + * @param array $ids + * @return array + */ + public function get(array $ids) + { + $select = $this->tierpriceResource->getConnection()->select()->from($this->tierpriceResource->getMainTable()); + return $this->tierpriceResource->getConnection()->fetchAll( + $select->where($this->getEntityLinkField() . ' IN (?)', $ids) + ); + } + + /** + * Update tier prices. + * + * @param array $prices + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function update(array $prices) + { + $connection = $this->tierpriceResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) { + $this->tierpriceResource->getConnection()->insertOnDuplicate( + $this->tierpriceResource->getMainTable(), + $pricesBunch, + ['value', 'percentage_value'] + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotSaveException( + __('Could not save Tier Prices'), + $e + ); + } + } + + /** + * Replace prices. + * + * @param array $prices + * @param array $ids + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function replace(array $prices, array $ids) + { + $connection = $this->tierpriceResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) { + $this->tierpriceResource->getConnection()->delete( + $this->tierpriceResource->getMainTable(), + [$this->getEntityLinkField() . ' IN (?)' => $idsBunch] + ); + } + + foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) { + $this->tierpriceResource->getConnection()->insertMultiple( + $this->tierpriceResource->getMainTable(), + $pricesBunch + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotSaveException( + __('Could not replace Tier Prices'), + $e + ); + } + } + + /** + * Delete tier prices by IDs. + * + * @param array $ids + * @return void + * @throws \Magento\Framework\Exception\CouldNotDeleteException + */ + public function delete(array $ids) + { + $connection = $this->tierpriceResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) { + $this->tierpriceResource->getConnection()->delete( + $this->tierpriceResource->getMainTable(), + ['value_id IN (?)' => $idsBunch] + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotDeleteException( + __('Could not delete Tier Prices'), + $e + ); + } + } + + /** + * Get link field. + * + * @return string + */ + public function getEntityLinkField() + { + return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php new file mode 100644 index 0000000000000..9df60a63007b1 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -0,0 +1,360 @@ +tierPricePersistence = $tierPricePersistence; + $this->tierPriceValidator = $tierPriceValidator; + $this->tierPriceFactory = $tierPriceFactory; + $this->priceIndexer = $priceIndexer; + $this->productIdLocator = $productIdLocator; + $this->config = $config; + $this->typeList = $typeList; + } + + /** + * {@inheritdoc} + */ + public function get(array $skus) + { + $skus = $this->tierPriceValidator->validateSkus($skus); + + return $this->getExistingPrices($skus); + } + + /** + * {@inheritdoc} + */ + public function update(array $prices) + { + $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $result = $this->tierPriceValidator->retrieveValidationResult($prices, $this->getExistingPrices($skus, true)); + $prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds()); + $formattedPrices = $this->retrieveFormattedPrices($prices); + $this->tierPricePersistence->update($formattedPrices); + $this->reindexPrices($affectedIds); + $this->invalidateFullPageCache(); + + return $result->getFailedItems(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $prices) + { + $result = $this->tierPriceValidator->retrieveValidationResult($prices); + $prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds()); + $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); + $formattedPrices = $this->retrieveFormattedPrices($prices); + $this->tierPricePersistence->replace($formattedPrices, $affectedIds); + $this->reindexPrices($affectedIds); + $this->invalidateFullPageCache(); + + return $result->getFailedItems(); + } + + /** + * {@inheritdoc} + */ + public function delete(array $prices) + { + $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); + $result = $this->tierPriceValidator->retrieveValidationResult($prices); + $prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds()); + $priceIds = $this->retrieveAffectedPriceIds($prices); + $this->tierPricePersistence->delete($priceIds); + $this->reindexPrices($affectedIds); + $this->invalidateFullPageCache(); + + return $result->getFailedItems(); + } + + /** + * Get existing prices by SKUs. + * + * @param array $skus + * @param bool $groupBySku [optional] + * @return array + */ + private function getExistingPrices(array $skus, $groupBySku = false) + { + $ids = $this->retrieveAffectedIds($skus); + $rawPrices = $this->tierPricePersistence->get($ids); + $prices = []; + + foreach ($rawPrices as $rawPrice) { + $sku = $this->retrieveSkuById($rawPrice[$this->tierPricePersistence->getEntityLinkField()], $skus); + $price = $this->tierPriceFactory->create($rawPrice, $sku); + if ($groupBySku) { + $prices[$sku][] = $price; + } else { + $prices[] = $price; + } + } + + return $prices; + } + + /** + * Retrieve formatted prices. + * + * @param array $prices + * @return array + */ + private function retrieveFormattedPrices(array $prices) + { + $formattedPrices = []; + + foreach ($prices as $price) { + $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()]); + $ids = array_keys($idsBySku[$price->getSku()]); + foreach ($ids as $id) { + $formattedPrices[] = $this->tierPriceFactory->createSkeleton($price, $id); + } + } + + return $formattedPrices; + } + + /** + * Retrieve affected product IDs for prices. + * + * @param TierPriceInterface[] $prices + * @return array + */ + private function retrieveAffectedProductIdsForPrices(array $prices) + { + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + + return $this->retrieveAffectedIds($skus); + } + + /** + * Retrieve affected product IDs. + * + * @param array $skus + * @return array + */ + private function retrieveAffectedIds(array $skus) + { + $affectedIds = []; + + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productId) { + $affectedIds = array_merge($affectedIds, array_keys($productId)); + } + + return array_unique($affectedIds); + } + + /** + * Retrieve affected price IDs. + * + * @param array $prices + * @return array + */ + private function retrieveAffectedPriceIds(array $prices) + { + $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); + $formattedPrices = $this->retrieveFormattedPrices($prices); + $existingPrices = $this->tierPricePersistence->get($affectedIds); + $priceIds = []; + + foreach ($formattedPrices as $price) { + $priceIds[] = $this->retrievePriceId($price, $existingPrices); + } + + return $priceIds; + } + + /** + * Look through provided price in list of existing prices and retrieve it's Id. + * + * @param array $price + * @param array $existingPrices + * @return int|null + */ + private function retrievePriceId(array $price, array $existingPrices) + { + $linkField = $this->tierPricePersistence->getEntityLinkField(); + + foreach ($existingPrices as $existingPrice) { + if ($existingPrice['all_groups'] == $price['all_groups'] + && $existingPrice['customer_group_id'] == $price['customer_group_id'] + && $existingPrice['qty'] == $price['qty'] + && $this->isCorrectPriceValue($existingPrice, $price) + && $existingPrice[$linkField] == $price[$linkField] + ) { + return $existingPrice['value_id']; + } + } + + return null; + } + + /** + * Check that price value or price percentage value is not equal to 0 and is not similar with existing value. + * + * @param array $existingPrice + * @param array $price + * @return bool + */ + private function isCorrectPriceValue(array $existingPrice, array $price) + { + return ($existingPrice['value'] != 0 && $existingPrice['value'] == $price['value']) + || ($existingPrice['percentage_value'] !== null + && $existingPrice['percentage_value'] == $price['percentage_value']); + } + + /** + * Retrieve SKU by product ID. + * + * @param int $id + * @param array $skus + * @return string|null + */ + private function retrieveSkuById($id, $skus) + { + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) { + if (isset($ids[$id])) { + return $sku; + } + } + + return null; + } + + /** + * Invalidate full page cache. + * + * @return void + */ + private function invalidateFullPageCache() + { + if ($this->config->isEnabled()) { + $this->typeList->invalidate('full_page'); + } + } + + /** + * Reindex prices. + * + * @param array $ids + * @return void + */ + private function reindexPrices(array $ids) + { + foreach (array_chunk($ids, $this->indexerChunkValue) as $affectedIds) { + $this->priceIndexer->execute($affectedIds); + } + } + + /** + * Remove prices from price list by id list. + * + * @param array $prices + * @param array $ids + * @return array + */ + private function removeIncorrectPrices(array $prices, array $ids) + { + foreach ($ids as $id) { + unset($prices[$id]); + } + + return $prices; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php b/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php new file mode 100644 index 0000000000000..08d75616ab5ca --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php @@ -0,0 +1,78 @@ +productIdLocator = $productIdLocator; + $this->productRepository = $productRepository; + } + + /** + * Retrieve not found or invalid SKUs which product types are included to allowed types list. + * + * @param array $skus + * @param array $allowedProductTypes + * @param int|null $allowedPriceTypeValue + * @return array + */ + public function retrieveInvalidSkuList(array $skus, array $allowedProductTypes, $allowedPriceTypeValue = null) + { + $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus); + $existingSkus = array_keys($idsBySku); + $skuDiff = array_udiff( + $skus, + $existingSkus, + 'strcasecmp' + ); + + foreach ($idsBySku as $sku => $ids) { + foreach ($ids as $type) { + $valueTypeIsAllowed = false; + + if ($allowedPriceTypeValue + && $type == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + && $this->productRepository->get($sku)->getPriceType() != $allowedPriceTypeValue + ) { + $valueTypeIsAllowed = true; + } + + if (!in_array($type, $allowedProductTypes) || $valueTypeIsAllowed) { + $skuDiff[] = $sku; + break; + } + } + } + + return $skuDiff; + } + + /** + * Filter invalid values in SKUs list. + * + * @param array $skus + * @param array $allowedProductTypes + * @param int|null $allowedPriceTypeValue + * @return array + */ + public function filterSkuList(array $skus, array $allowedProductTypes, $allowedPriceTypeValue = null) + { + $failedItems = $this->retrieveInvalidSkuList($skus, $allowedProductTypes, $allowedPriceTypeValue); + return array_diff($skus, $failedItems); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/Validation/Result.php b/app/code/Magento/Catalog/Model/Product/Price/Validation/Result.php new file mode 100644 index 0000000000000..fd02d72f51036 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/Validation/Result.php @@ -0,0 +1,82 @@ +priceUpdateResultFactory = $priceUpdateResultFactory; + } + + /** + * Add failed price identified, message and message parameters, that occurred during price update. + * + * @param int $id Failed price identified. + * @param string $message Failure reason message. + * @param array $parameters (optional). Placeholder values in ['placeholder key' => 'placeholder value'] format + * for failure reason message. + * @return void + */ + public function addFailedItem($id, $message, array $parameters = []) + { + $this->failedItems[$id][] = [ + 'message' => $message, + 'parameters' => $parameters + ]; + } + + /** + * Get ids of rows, that contained errors during price update. + * + * @return int[] + */ + public function getFailedRowIds() + { + return $this->failedItems ? array_keys($this->failedItems) : []; + } + + /** + * Get price update errors, that occurred during price update. + * + * @return \Magento\Catalog\Api\Data\PriceUpdateResultInterface[] + */ + public function getFailedItems() + { + $failedItems = []; + + foreach ($this->failedItems as $items) { + foreach ($items as $failedRecord) { + $resultItem = $this->priceUpdateResultFactory->create(); + $resultItem->setMessage($failedRecord['message']); + $resultItem->setParameters($failedRecord['parameters']); + $failedItems[] = $resultItem; + } + } + + return $failedItems; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Price/Validation/TierPriceValidator.php b/app/code/Magento/Catalog/Model/Product/Price/Validation/TierPriceValidator.php new file mode 100644 index 0000000000000..b39423c8894de --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Price/Validation/TierPriceValidator.php @@ -0,0 +1,478 @@ +productIdLocator = $productIdLocator; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->filterBuilder = $filterBuilder; + $this->customerGroupRepository = $customerGroupRepository; + $this->websiteRepository = $websiteRepository; + $this->tierPricePersistence = $tierPricePersistence; + $this->validationResult = $validationResult; + $this->invalidSkuProcessor = $invalidSkuProcessor; + $this->allowedProductTypes = $allowedProductTypes; + } + + /** + * Validate SKU. + * + * @param array $skus + * @return array + */ + public function validateSkus(array $skus) + { + return $this->invalidSkuProcessor->filterSkuList($skus, $this->allowedProductTypes); + } + + /** + * Validate that prices have appropriate values and are unique and return result. + * + * @param array $prices + * @param array $existingPrices + * @return \Magento\Catalog\Model\Product\Price\Validation\Result $validationResult + */ + public function retrieveValidationResult(array $prices, array $existingPrices = []) + { + $validationResult = clone $this->validationResult; + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $skuDiff = $this->invalidSkuProcessor->retrieveInvalidSkuList($skus, $this->allowedProductTypes); + $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus); + + $pricesBySku = []; + + foreach ($prices as $price) { + $pricesBySku[$price->getSku()][] = $price; + } + + foreach ($prices as $key => $price) { + $this->checkSku($price, $key, $skuDiff, $validationResult); + $this->checkPrice($price, $key, $validationResult); + $ids = isset($idsBySku[$price->getSku()]) ? $idsBySku[$price->getSku()] : []; + $this->checkPriceType($price, $ids, $key, $validationResult); + $this->checkQuantity($price, $key, $validationResult); + $this->checkWebsite($price, $key, $validationResult); + if (isset($pricesBySku[$price->getSku()])) { + $this->checkUnique($price, $pricesBySku, $key, $validationResult); + } + $this->checkUnique($price, $existingPrices, $key, $validationResult); + $this->checkGroup($price, $key, $validationResult); + } + + return $validationResult; + } + + /** + * Check that sku value is correct. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param int $key + * @param array $invalidSkus + * @param Result $validationResult + * @return void + */ + private function checkSku( + \Magento\Catalog\Api\Data\TierPriceInterface $price, + $key, + array $invalidSkus, + Result $validationResult + ) { + if (!$price->getSku() || in_array($price->getSku(), $invalidSkus)) { + $validationResult->addFailedItem( + $key, + __( + 'Invalid attribute SKU = %SKU. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Verify that price value is correct. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkPrice(\Magento\Catalog\Api\Data\TierPriceInterface $price, $key, Result $validationResult) + { + if ( + null === $price->getPrice() + || $price->getPrice() < 0 + || ($price->getPriceType() === \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT + && $price->getPrice() > 100 + ) + ) { + $validationResult->addFailedItem( + $key, + __( + 'Invalid attribute Price = %price. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'price' => '%price', + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'price' => $price->getPrice(), + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Verify that price type is correct. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param array $ids + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkPriceType( + \Magento\Catalog\Api\Data\TierPriceInterface $price, + array $ids, + $key, + Result $validationResult + ) { + if ( + !in_array( + $price->getPriceType(), + [ + \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_FIXED, + \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT + ] + ) + || (array_search(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE, $ids) + && $price->getPriceType() !== \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT) + ) { + $validationResult->addFailedItem( + $key, + __( + 'Invalid attribute Price Type = %priceType. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'price' => '%price', + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'priceType' => $price->getPriceType(), + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Verify that product quantity is correct. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkQuantity(\Magento\Catalog\Api\Data\TierPriceInterface $price, $key, Result $validationResult) + { + if ($price->getQuantity() < 1) { + $validationResult->addFailedItem( + $key, + __( + 'Invalid attribute Quantity = %qty. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Verify that website exists. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkWebsite(\Magento\Catalog\Api\Data\TierPriceInterface $price, $key, Result $validationResult) + { + try { + $this->websiteRepository->getById($price->getWebsiteId()); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $validationResult->addFailedItem( + $key, + __( + 'Invalid attribute Website ID = %websiteId. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Check website value is unique. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $tierPrice + * @param array $prices + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkUnique( + \Magento\Catalog\Api\Data\TierPriceInterface $tierPrice, + array $prices, + $key, + Result $validationResult + ) { + if (isset($prices[$tierPrice->getSku()])) { + foreach ($prices[$tierPrice->getSku()] as $price) { + if (strtolower($price->getCustomerGroup()) === strtolower($tierPrice->getCustomerGroup()) + && $price->getQuantity() == $tierPrice->getQuantity() + && ( + ($price->getWebsiteId() == $this->allWebsitesValue + || $tierPrice->getWebsiteId() == $this->allWebsitesValue) + && $price->getWebsiteId() != $tierPrice->getWebsiteId() + ) + ) { + $validationResult->addFailedItem( + $key, + __( + 'We found a duplicate website, tier price, customer group and quantity: ' + . 'Customer Group = %customerGroup, Website ID = %websiteId, Quantity = %qty. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, ' + . 'Customer Group: %customerGroup, Quantity: %qty.', + [ + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + } + } + + /** + * Check customer group exists and has correct value. + * + * @param \Magento\Catalog\Api\Data\TierPriceInterface $price + * @param int $key + * @param Result $validationResult + * @return void + */ + private function checkGroup(\Magento\Catalog\Api\Data\TierPriceInterface $price, $key, Result $validationResult) + { + $customerGroup = strtolower($price->getCustomerGroup()); + + if ($customerGroup != $this->allGroupsValue && false === $this->retrieveGroupValue($customerGroup)) { + $validationResult->addFailedItem( + $key, + __( + 'No such entity with Customer Group = %customerGroup. ' + . 'Row ID: SKU = %SKU, Website ID: %websiteId, Customer Group: %customerGroup, Quantity: %qty.', + [ + 'SKU' => '%SKU', + 'websiteId' => '%websiteId', + 'customerGroup' => '%customerGroup', + 'qty' => '%qty' + ] + ), + [ + 'SKU' => $price->getSku(), + 'websiteId' => $price->getWebsiteId(), + 'customerGroup' => $price->getCustomerGroup(), + 'qty' => $price->getQuantity() + ] + ); + } + } + + /** + * Retrieve customer group id by code. + * + * @param string $code + * @return int|bool + */ + private function retrieveGroupValue($code) + { + if (!isset($this->customerGroupsByCode[$code])) { + $searchCriteria = $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder->setField('customer_group_code')->setValue($code)->create() + ] + ); + $items = $this->customerGroupRepository->getList($searchCriteria->create())->getItems(); + $item = array_shift($items); + + if (!$item) { + return false; + } + + $this->customerGroupsByCode[strtolower($item->getCode())] = $item->getId(); + } + + return $this->customerGroupsByCode[$code]; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/PriceModifier.php b/app/code/Magento/Catalog/Model/Product/PriceModifier.php index 39d66054db014..2256ff3ae8d64 100644 --- a/app/code/Magento/Catalog/Model/Product/PriceModifier.php +++ b/app/code/Magento/Catalog/Model/Product/PriceModifier.php @@ -1,6 +1,6 @@ getCanShowPrice() !== false; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php new file mode 100644 index 0000000000000..f019d5621e02b --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php @@ -0,0 +1,21 @@ +_catalogProductOption = $catalogProductOption; $this->_eavConfig = $eavConfig; @@ -195,6 +204,8 @@ public function __construct( $this->_filesystem = $filesystem; $this->_logger = $logger; $this->productRepository = $productRepository; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); } /** @@ -394,8 +405,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p $product->prepareCustomOptions(); $buyRequest->unsetData('_processing_params'); // One-time params only - $product->addCustomOption('info_buyRequest', serialize($buyRequest->getData())); - + $product->addCustomOption('info_buyRequest', $this->serializer->serialize($buyRequest->getData())); if ($options) { $optionIds = array_keys($options); $product->addCustomOption('option_ids', implode(',', $optionIds)); @@ -645,7 +655,7 @@ public function getOrderOptions($product) $optionArr = []; $info = $product->getCustomOption('info_buyRequest'); if ($info) { - $optionArr['info_buyRequest'] = unserialize($info->getValue()); + $optionArr['info_buyRequest'] = $this->serializer->unserialize($info->getValue()); } $optionIds = $product->getCustomOption('option_ids'); @@ -1092,4 +1102,15 @@ public function getAssociatedProducts($product) { return []; } + + /** + * Check if product can be potentially buyed from the category page or some other list + * + * @param \Magento\Catalog\Model\Product $product + * @return bool + */ + public function isPossibleBuyFromList($product) + { + return !$this->hasRequiredOptions($product); + } } diff --git a/app/code/Magento/Catalog/Model/Product/Type/Pool.php b/app/code/Magento/Catalog/Model/Product/Type/Pool.php index 84058a2fed4d5..0139c3cdae0fc 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Pool.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Pool.php @@ -1,6 +1,6 @@ getExtensionAttributes(); - $websiteId = $extensionAttributes && $extensionAttributes->getWebsiteId() - ? $extensionAttributes->getWebsiteId() - : $websiteId; + $priceWebsiteId = $websiteId; + if (isset($extensionAttributes) && is_numeric($extensionAttributes->getWebsiteId())) { + $priceWebsiteId = (string)$extensionAttributes->getWebsiteId(); + } $prices[] = [ - 'website_id' => $websiteId, + 'website_id' => $priceWebsiteId, 'cust_group' => $price->getCustomerGroupId(), 'website_price' => $price->getValue(), 'price' => $price->getValue(), diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price/Factory.php b/app/code/Magento/Catalog/Model/Product/Type/Price/Factory.php index 257b0e8d0529d..6d1fc78798c99 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price/Factory.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price/Factory.php @@ -1,6 +1,6 @@ metadataPool = $metadataPool; + $this->collectionFactory = $collectionFactory; + $this->idsLimit = (int)$idsLimit; + } + + /** + * {@inheritdoc} + */ + public function retrieveProductIdsBySkus(array $skus) + { + $neededSkus = []; + foreach ($skus as $sku) { + $unifiedSku = strtolower(trim($sku)); + if (!isset($this->idsBySku[$unifiedSku])) { + $neededSkus[] = $sku; + } + } + + if (!empty($neededSkus)) { + /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ + $collection = $this->collectionFactory->create(); + $collection->addFieldToFilter(\Magento\Catalog\Api\Data\ProductInterface::SKU, ['in' => $neededSkus]); + $linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + + foreach ($collection as $item) { + $this->idsBySku[strtolower(trim($item->getSku()))][$item->getData($linkField)] = $item->getTypeId(); + } + } + + $productIds = []; + foreach ($skus as $sku) { + $unifiedSku = strtolower(trim($sku)); + if (isset($this->idsBySku[$unifiedSku])) { + $productIds[$sku] = $this->idsBySku[$unifiedSku]; + } + } + $this->truncateToLimit(); + return $productIds; + } + + /** + * Cleanup IDs by SKU cache more than some limit. + * + * @return void + */ + private function truncateToLimit() + { + if (count($this->idsBySku) > $this->idsLimit) { + $this->idsBySku = array_slice($this->idsBySku, round($this->idsLimit / -2)); + } + } +} diff --git a/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php b/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php new file mode 100644 index 0000000000000..9016dc07d9be1 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php @@ -0,0 +1,21 @@ +providers[$type]->getLinkedProducts($product); $converter = $this->converterPool->getConverter($type); $output = []; + $sorterItems = []; foreach ($products as $item) { $output[$item->getId()] = $converter->convert($item); } - return $output; + + foreach ($output as $item) { + $itemPosition = $item['position']; + if (!isset($sorterItems[$itemPosition])) { + $sorterItems[$itemPosition] = $item; + } else { + $newPosition = $itemPosition + 1; + $sorterItems[$newPosition] = $item; + } + } + ksort($sorterItems); + return $sorterItems; } } diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider/Crosssell.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider/Crosssell.php index f1955166901a5..4140e18fb13d6 100644 --- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider/Crosssell.php +++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider/Crosssell.php @@ -1,6 +1,6 @@ productFactory->create(); if ($this->storeManager->hasSingleStore()) { @@ -313,15 +314,22 @@ protected function initializeProductData(array $productData, $createNew) */ private function assignProductToWebsites(\Magento\Catalog\Model\Product $product) { + $websiteIds = $product->getWebsiteIds(); + if (!$this->storeManager->hasSingleStore()) { - if ($this->storeManager->getStore()->getCode() == \Magento\Store\Model\Store::ADMIN_CODE) { - $websiteIds = array_keys($this->storeManager->getWebsites()); - } else { - $websiteIds = [$this->storeManager->getStore()->getWebsiteId()]; - } + $websiteIds = array_unique( + array_merge( + $websiteIds, + [$this->storeManager->getStore()->getWebsiteId()] + ) + ); + } - $product->setWebsiteIds(array_unique(array_merge($product->getWebsiteIds(), $websiteIds))); + if ($this->storeManager->getStore(true)->getCode() == \Magento\Store\Model\Store::ADMIN_CODE) { + $websiteIds = array_keys($this->storeManager->getWebsites()); } + + $product->setWebsiteIds($websiteIds); } /** @@ -430,8 +438,15 @@ private function processLinks(\Magento\Catalog\Api\Data\ProductInterface $produc } /** - * @param ProductInterface $product - * @param array $mediaGalleryEntries + * Process Media gallery data before save product. + * + * Compare Media Gallery Entries Data with existing Media Gallery + * * If Media entry has not value_id set it as new + * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag + * * Merge Existing and new media gallery + * + * @param ProductInterface $product contains only existing media gallery items + * @param array $mediaGalleryEntries array which contains all media gallery items * @return $this * @throws InputException * @throws StateException @@ -441,11 +456,10 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE { $existingMediaGallery = $product->getMediaGallery('images'); $newEntries = []; + $entriesById = []; if (!empty($existingMediaGallery)) { - $entriesById = []; foreach ($mediaGalleryEntries as $entry) { - if (isset($entry['id'])) { - $entry['value_id'] = $entry['id']; + if (isset($entry['value_id'])) { $entriesById[$entry['value_id']] = $entry; } else { $newEntries[] = $entry; @@ -454,6 +468,9 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE foreach ($existingMediaGallery as $key => &$existingEntry) { if (isset($entriesById[$existingEntry['value_id']])) { $updatedEntry = $entriesById[$existingEntry['value_id']]; + if ($updatedEntry['file'] === null) { + unset($updatedEntry['file']); + } $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); } else { //set the removed flag @@ -481,11 +498,18 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE } /** @var ImageContentInterface $contentDataObject */ $contentDataObject = $this->contentFactory->create() - ->setName($newEntry['content'][ImageContentInterface::NAME]) - ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) - ->setType($newEntry['content'][ImageContentInterface::TYPE]); + ->setName($newEntry['content']['data'][ImageContentInterface::NAME]) + ->setBase64EncodedData($newEntry['content']['data'][ImageContentInterface::BASE64_ENCODED_DATA]) + ->setType($newEntry['content']['data'][ImageContentInterface::TYPE]); $newEntry['content'] = $contentDataObject; $this->processNewMediaGalleryEntry($product, $newEntry); + + $finalGallery = $product->getData('media_gallery'); + $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); + $entriesById[$newEntryId] = $newEntry; + $finalGallery['images'][$newEntryId] = $newEntry; + $product->setData('media_gallery', $finalGallery); } return $this; } @@ -513,8 +537,6 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO $productDataArray = $this->extensibleDataObjectConverter ->toNestedArray($product, [], \Magento\Catalog\Api\Data\ProductInterface::class); $productDataArray = array_replace($productDataArray, $product->getData()); - unset($productDataArray['media_gallery']); - $ignoreLinksFlag = $product->getData('ignore_links_flag'); $productLinks = null; if (!$ignoreLinksFlag && $ignoreLinksFlag !== null) { @@ -524,8 +546,8 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO $product = $this->initializeProductData($productDataArray, empty($existingProduct)); $this->processLinks($product, $productLinks); - if (isset($productDataArray['media_gallery_entries'])) { - $this->processMediaGallery($product, $productDataArray['media_gallery_entries']); + if (isset($productDataArray['media_gallery'])) { + $this->processMediaGallery($product, $productDataArray['media_gallery']['images']); } if (!$product->getOptionsReadonly()) { @@ -633,6 +655,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr $collection->load(); + $collection->addCategoryIds(); $searchResult = $this->searchResultsFactory->create(); $searchResult->setSearchCriteria($searchCriteria); $searchResult->setItems($collection->getItems()); diff --git a/app/code/Magento/Catalog/Model/ProductType.php b/app/code/Magento/Catalog/Model/ProductType.php index 30119223ef74c..c1c0ecc82c4a0 100644 --- a/app/code/Magento/Catalog/Model/ProductType.php +++ b/app/code/Magento/Catalog/Model/ProductType.php @@ -2,7 +2,7 @@ /** * Product type * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model; diff --git a/app/code/Magento/Catalog/Model/ProductTypeList.php b/app/code/Magento/Catalog/Model/ProductTypeList.php index ad36a7736de66..3e9946f43ea95 100644 --- a/app/code/Magento/Catalog/Model/ProductTypeList.php +++ b/app/code/Magento/Catalog/Model/ProductTypeList.php @@ -2,7 +2,7 @@ /** * Product type provider * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model; diff --git a/app/code/Magento/Catalog/Model/ProductTypes/Config.php b/app/code/Magento/Catalog/Model/ProductTypes/Config.php index a80692cfaf945..2db4b59878e0c 100644 --- a/app/code/Magento/Catalog/Model/ProductTypes/Config.php +++ b/app/code/Magento/Catalog/Model/ProductTypes/Config.php @@ -1,23 +1,32 @@ productRepository->get($productWebsiteLink->getSku()); $product->setWebsiteIds(array_merge($product->getWebsiteIds(), [$productWebsiteLink->getWebsiteId()])); try { - $product->save(); + $this->productRepository->save($product); } catch (\Exception $e) { throw new CouldNotSaveException( __( @@ -68,7 +68,7 @@ public function deleteById($sku, $websiteId) $product->setWebsiteIds(array_diff($product->getWebsiteIds(), [$websiteId])); try { - $product->save(); + $this->productRepository->save($product); } catch (\Exception $e) { throw new CouldNotSaveException( __( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php index e026d6540aceb..05e8e0a14e8b5 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php @@ -1,6 +1,6 @@ getChildrenCount()) { $object->setChildrenCount(0); } - + $object->setAttributeSetId( + $object->getAttributeSetId() ?: $this->getEntityType()->getDefaultAttributeSetId() + ); if ($object->isObjectNew()) { if ($object->getPosition() === null) { $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); @@ -573,22 +580,29 @@ public function getIsActiveAttributeId() */ public function findWhereAttributeIs($entityIdsFilter, $attribute, $expectedValue) { - $linkField = $this->getLinkField(); - $bind = ['attribute_id' => $attribute->getId(), 'value' => $expectedValue]; - $selectEntities = $this->getConnection()->select()->from( - ['ce' => $this->getTable('catalog_category_entity')], - ['entity_id'] - )->joinLeft( - ['ci' => $attribute->getBackend()->getTable()], - "ci.{$linkField} = ce.{$linkField} AND attribute_id = :attribute_id", - ['value'] - )->where( - 'ci.value = :value' - )->where( - 'ce.entity_id IN (?)', - $entityIdsFilter - ); - return $this->getConnection()->fetchCol($selectEntities, $bind); + $entityIdsFilterHash = md5(serialize($entityIdsFilter)); + + if (!isset($this->entitiesWhereAttributesIs[$entityIdsFilterHash][$attribute->getId()][$expectedValue])) { + $linkField = $this->getLinkField(); + $bind = ['attribute_id' => $attribute->getId(), 'value' => $expectedValue]; + $selectEntities = $this->getConnection()->select()->from( + ['ce' => $this->getTable('catalog_category_entity')], + ['entity_id'] + )->joinLeft( + ['ci' => $attribute->getBackend()->getTable()], + "ci.{$linkField} = ce.{$linkField} AND attribute_id = :attribute_id", + ['value'] + )->where( + 'ci.value = :value' + )->where( + 'ce.entity_id IN (?)', + $entityIdsFilter + ); + $this->entitiesWhereAttributesIs[$entityIdsFilterHash][$attribute->getId()][$expectedValue] = + $this->getConnection()->fetchCol($selectEntities, $bind); + } + + return $this->entitiesWhereAttributesIs[$entityIdsFilterHash][$attribute->getId()][$expectedValue]; } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/AggregateCount.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/AggregateCount.php index 4599fc4a8428a..7d55ebc3e9f91 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/AggregateCount.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/AggregateCount.php @@ -1,6 +1,6 @@ getData(self::APPLY_TO)) { - if (is_array($this->getData(self::APPLY_TO))) { - return $this->getData(self::APPLY_TO); - } - return explode(',', $this->getData(self::APPLY_TO)); - } else { - return []; + $applyTo = $this->_getData(self::APPLY_TO) ?: []; + if (!is_array($applyTo)) { + $applyTo = explode(',', $applyTo); } + return $applyTo; } /** @@ -420,6 +417,9 @@ public function isIndexable() if ($this->getAttributeCode() == 'price') { return false; } + if ($this->getAttributeCode() == 'visibility') { + return true; + } if (!$this->getIsFilterableInSearch() && !$this->getIsVisibleInAdvancedSearch() && !$this->getIsFilterable()) { return false; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Helper.php b/app/code/Magento/Catalog/Model/ResourceModel/Helper.php index 36dbf0b9739c7..d9490b418c37c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Helper.php @@ -1,6 +1,6 @@ productCategoryLink; } + + /** + * Extends parent method to be appropriate for product. + * Store id is required to correctly identify attribute value we are working with. + * + * {@inheritdoc} + */ + protected function getAttributeRow($entity, $object, $attribute) + { + $data = parent::getAttributeRow($entity, $object, $attribute); + $data['store_id'] = $object->getStoreId(); + return $data; + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php index 7075e1e11d493..46ecffcb6f442 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php @@ -1,6 +1,6 @@ moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -316,7 +323,11 @@ public function __construct( $this->_resourceHelper = $resourceHelper; $this->dateTime = $dateTime; $this->_groupManagement = $groupManagement; - $this->_productLimitationFilters = $this->createLimitationFilters(); + $productLimitationFactory = $productLimitationFactory ?: ObjectManager::getInstance()->get( + \Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory::class + ); + $this->_productLimitationFilters = $productLimitationFactory->create(); + $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); parent::__construct( $entityFactory, $logger, @@ -2181,14 +2192,25 @@ public function addMediaGalleryData() ); $mediaGalleries = []; - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); - + $linkField = $this->getProductEntityMetadata()->getLinkField(); + $items = $this->getItems(); + + $select->where( + 'entity.' . $linkField . ' IN (?)', + array_map( + function ($item) use ($linkField) { + return $item->getData($linkField); + }, + $items + ) + ); foreach ($this->getConnection()->fetchAll($select) as $row) { $mediaGalleries[$row[$linkField]][] = $row; } - foreach ($this->getItems() as $item) { - $mediaEntries = isset($mediaGalleries[$item->getId()]) ? $mediaGalleries[$item->getId()] : []; + foreach ($items as $item) { + $mediaEntries = isset($mediaGalleries[$item->getData($linkField)]) ? + $mediaGalleries[$item->getData($linkField)] : []; $this->getGalleryReadHandler()->addMediaDataToProduct($item, $mediaEntries); } @@ -2197,15 +2219,13 @@ public function addMediaGalleryData() } /** - * Get MetadataPool instance - * @return MetadataPool + * Get product entity metadata + * + * @return \Magento\Framework\EntityManager\EntityMetadataInterface */ - private function getMetadataPool() + public function getProductEntityMetadata() { - if (!$this->metadataPool) { - $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); - } - return $this->metadataPool; + return $this->metadataPool->getMetadata(ProductInterface::class); } /** @@ -2329,13 +2349,4 @@ public function getPricesCount() return $this->_pricesCount; } - - /** - * @return Collection\ProductLimitation - */ - private function createLimitationFilters() - { - return \Magento\Framework\App\ObjectManager::getInstance() - ->create(\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class); - } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php index aba330bea0651..28fcb4ab5c405 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php @@ -1,6 +1,6 @@ baseSelectProcessors = $baseSelectProcessors; + } + + /** + * @param Select $select + * @return Select + */ + public function process(Select $select) + { + foreach ($this->baseSelectProcessors as $baseSelectProcessor) { + $select = $baseSelectProcessor->process($select); + } + return $select; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Flat.php index 56f14d4c12577..2dd445377d6ea 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Flat.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Flat.php @@ -1,6 +1,6 @@ getConnection()->fetchAll($select); } + + /** + * Counts uses of this image. + * + * @param string $image + * @return int + */ + public function countImageUses($image) + { + $select = $this->getConnection()->select() + ->from([$this->getMainTableAlias() => $this->getMainTable()]) + ->where( + 'value = ?', + $image + ); + return count($this->getConnection()->fetchAll($select)); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/AbstractIndexer.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/AbstractIndexer.php index 44d9b1c0304a8..378a3ddf099ab 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/AbstractIndexer.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/AbstractIndexer.php @@ -1,6 +1,6 @@ joinLeft( ['e' => $this->getTable('catalog_product_entity')], 'e.' . $linkField .' = l.parent_id', - ['e.entity_id as parent_id'] + [] )->join( ['cs' => $this->getTable('store')], '', @@ -205,9 +205,17 @@ protected function _prepareRelationIndexSelect($parentIds = null) )->join( ['i' => $idxTable], 'l.child_id = i.entity_id AND cs.store_id = i.store_id', - ['attribute_id', 'store_id', 'value'] + [] )->group( - ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value'] + ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value', 'l.child_id'] + )->columns( + [ + 'parent_id' => 'e.entity_id', + 'attribute_id' => 'i.attribute_id', + 'store_id' => 'i.store_id', + 'value' => 'i.value', + 'source_id' => 'l.child_id' + ] ); if ($parentIds !== null) { $select->where('e.entity_id IN(?)', $parentIds); @@ -222,7 +230,7 @@ protected function _prepareRelationIndexSelect($parentIds = null) 'select' => $select, 'entity_field' => new \Zend_Db_Expr('l.parent_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php index e8d9889e68d59..76127b02d5fb9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php @@ -1,6 +1,6 @@ $productValueExpression, + 'source_id' => 'cpe.entity_id', ] ); @@ -116,7 +117,7 @@ protected function _prepareIndex($entityIds = null, $attributeId = null) 'select' => $select, 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php index c4eda1c987192..7a698ae595d19 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php @@ -1,6 +1,6 @@ $ifNullSql, + 'pid.entity_id', ] )->where( 'pid.attribute_id IN(?)', @@ -200,7 +201,7 @@ protected function _prepareSelectIndex($entityIds = null, $attributeId = null) 'select' => $select, 'entity_field' => new \Zend_Db_Expr('pid.entity_id'), 'website_field' => new \Zend_Db_Expr('pid.website_id'), - 'store_field' => new \Zend_Db_Expr('pid.store_id') + 'store_field' => new \Zend_Db_Expr('pid.store_id'), ] ); $query = $select->insertFromSelect($idxTable); @@ -221,11 +222,7 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu $connection = $this->getConnection(); // prepare multiselect attributes - if ($attributeId === null) { - $attrIds = $this->_getIndexableAttributes(true); - } else { - $attrIds = [$attributeId]; - } + $attrIds = $attributeId === null ? $this->_getIndexableAttributes(true) : [$attributeId]; if (!$attrIds) { return $this; @@ -247,20 +244,20 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu $productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value'); $select = $connection->select()->from( ['pvd' => $this->getTable('catalog_product_entity_varchar')], - [$productIdField, 'attribute_id'] + [] )->join( ['cs' => $this->getTable('store')], '', - ['store_id'] + [] )->joinLeft( ['pvs' => $this->getTable('catalog_product_entity_varchar')], "pvs.{$productIdField} = pvd.{$productIdField} AND pvs.attribute_id = pvd.attribute_id" . ' AND pvs.store_id=cs.store_id', - ['value' => $productValueExpression] + [] )->joinLeft( ['cpe' => $this->getTable('catalog_product_entity')], "cpe.{$productIdField} = pvd.{$productIdField}", - ['entity_id'] + [] )->where( 'pvd.store_id=?', $connection->getIfNullSql('pvs.store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID) @@ -272,6 +269,14 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu $attrIds )->where( 'cpe.entity_id IS NOT NULL' + )->columns( + [ + 'entity_id' => 'cpe.entity_id', + 'attribute_id' => 'attribute_id', + 'store_id' => 'cs.store_id', + 'value' => $productValueExpression, + 'source_id' => 'cpe.entity_id', + ] ); $statusCond = $connection->quoteInto('=?', ProductStatus::STATUS_ENABLED); @@ -289,30 +294,11 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu 'select' => $select, 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); - $i = 0; - $data = []; - $query = $select->query(); - while ($row = $query->fetch()) { - $values = explode(',', $row['value']); - foreach ($values as $valueId) { - if (isset($options[$row['attribute_id']][$valueId])) { - $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId]; - $i++; - if ($i % 10000 == 0) { - $this->_saveIndexData($data); - $data = []; - } - } - } - } - - $this->_saveIndexData($data); - unset($options); - unset($data); + $this->saveDataFromSelect($select, $options); return $this; } @@ -331,7 +317,7 @@ protected function _saveIndexData(array $data) $connection = $this->getConnection(); $connection->insertArray( $this->getIdxTable(), - ['entity_id', 'attribute_id', 'store_id', 'value'], + ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'], $data ); return $this; @@ -348,4 +334,31 @@ public function getIdxTable($table = null) { return $this->tableStrategy->getTableName('catalog_product_index_eav'); } + + /** + * @param \Magento\Framework\DB\Select $select + * @param array $options + * @return void + */ + private function saveDataFromSelect(\Magento\Framework\DB\Select $select, array $options) + { + $i = 0; + $data = []; + $query = $select->query(); + while ($row = $query->fetch()) { + $values = explode(',', $row['value']); + foreach ($values as $valueId) { + if (isset($options[$row['attribute_id']][$valueId])) { + $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId, $row['source_id']]; + $i++; + if ($i % 10000 == 0) { + $this->_saveIndexData($data); + $data = []; + } + } + } + } + + $this->_saveIndexData($data); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index ea72691ea0039..ffaf8a5d100d3 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -1,12 +1,13 @@ storeManager = $storeManager; $this->resource = $resourceConnection; $this->customerSession = $customerSession; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -58,24 +68,27 @@ public function build($productId) $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); - return [$this->resource->getConnection()->select() + $priceSelect = $this->resource->getConnection()->select() ->from(['parent' => $productTable], '') ->joinInner( ['link' => $this->resource->getTableName('catalog_product_relation')], "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $this->resource->getTableName('catalog_product_index_price')], - 't.entity_id = child.entity_id', + sprintf('t.entity_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->order('t.min_price ' . Select::SQL_ASC) - ->limit(1)]; + ->limit(1); + $priceSelect = $this->baseSelectProcessor->process($priceSelect); + + return [$priceSelect]; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 289445ae2daf0..8a97d43a18d7e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -1,6 +1,6 @@ _prepareDefaultFinalPriceTable(); + + $select = $this->getSelect($entityIds, $type); + $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable(), [], false); + $this->getConnection()->query($query); + return $this; + } + + /** + * Forms Select for collecting price related data for final price index table + * Next types of prices took into account: default, special, tier price + * Moved to protected for possible reusing + * + * @param int|array $entityIds Ids for filtering output result + * @param string|null $type Type for filtering output result by specified product type (all if null) + * @return \Magento\Framework\DB\Select + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function getSelect($entityIds = null, $type = null) + { $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); $connection = $this->getConnection(); $select = $connection->select()->from( @@ -368,13 +387,10 @@ protected function prepareFinalPriceDataForType($entityIds, $type) 'select' => $select, 'entity_field' => new \Zend_Db_Expr('e.entity_id'), 'website_field' => new \Zend_Db_Expr('cw.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); - - $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable(), [], false); - $connection->query($query); - return $this; + return $select; } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php index ddaf7adef8c14..bacf79408ca6b 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php @@ -1,6 +1,6 @@ fetchOne($select, $bind); } + /** + * Check if product has links. + * + * @param int $parentId ID of product + * @return bool + */ + public function hasProductLinks($parentId) + { + $connection = $this->getConnection(); + $select = $connection->select()->from( + $this->getMainTable(), + ['count' => new \Zend_Db_Expr('COUNT(*)')] + )->where( + 'product_id = :product_id' + ) ; + + return $connection->fetchOne( + $select, + [ + 'product_id' => $parentId + ] + ) > 0; + } + /** * Save Product Links process * diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Collection.php index 999d70d808102..da9a46c9e106d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Collection.php @@ -1,6 +1,6 @@ getMetadataPool()->getMetadata(ProductInterface::class)->getIdentifierField(); + $identifierField = $this->getProductEntityMetadata()->getIdentifierField(); $this->getSelect()->where("product_entity_table.$identifierField IN (?)", $products); $this->_hasLinkFilter = true; } @@ -279,7 +200,7 @@ protected function _joinLinks() $connection->quoteInto('links.link_type_id = ?', $this->_linkTypeId), ]; $joinType = 'join'; - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); + $linkField = $this->getProductEntityMetadata()->getLinkField(); if ($this->getProduct() && $this->getProduct()->getId()) { $linkFieldId = $this->getProduct()->getData( $linkField @@ -422,19 +343,6 @@ public function addLinkAttributeToFilter($code, $condition) return $this; } - /** - * Get MetadataPool instance - * @return MetadataPool - * @deprecated - */ - private function getMetadataPool() - { - if (!$this->metadataPool) { - $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); - } - return $this->metadataPool; - } - /** * Join Product To Links * @return void @@ -442,11 +350,15 @@ private function getMetadataPool() private function joinProductsToLinks() { if ($this->_hasLinkFilter) { - $metaDataPool = $this->getMetadataPool()->getMetadata(ProductInterface::class); + $metaDataPool = $this->getProductEntityMetadata(); $linkField = $metaDataPool->getLinkField(); $entityTable = $metaDataPool->getEntityTable(); $this->getSelect() - ->join(['product_entity_table' => $entityTable], "links.product_id = product_entity_table.$linkField", []); + ->join( + ['product_entity_table' => $entityTable], + "links.product_id = product_entity_table.$linkField", + [] + ); } } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php index ad987c6c8b4b3..24a7210d8ff12 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php @@ -1,6 +1,6 @@ storeManager = $storeManager; $this->resource = $resourceConnection; $this->eavConfig = $eavConfig; $this->catalogHelper = $catalogHelper; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -74,28 +84,29 @@ public function build($productId) "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField), ['entity_id'] )->joinInner( ['t' => $priceAttribute->getBackendTable()], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.attribute_id = ?', $priceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->order('t.value ' . Select::SQL_ASC) ->limit(1); - - $priceSelectDefault = clone $priceSelect; - $priceSelectDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID); - $select[] = $priceSelectDefault; + $priceSelect = $this->baseSelectProcessor->process($priceSelect); if (!$this->catalogHelper->isPriceGlobal()) { - $priceSelect->where('t.store_id = ?', $this->storeManager->getStore()->getId()); - $select[] = $priceSelect; + $priceSelectStore = clone $priceSelect; + $priceSelectStore->where('t.store_id = ?', $this->storeManager->getStore()->getId()); + $selects[] = $priceSelectStore; } - return $select; + $priceSelect->where('t.store_id = ?', Store::DEFAULT_STORE_ID); + $selects[] = $priceSelect; + + return $selects; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index 792a8f5b86d10..17ce55b1252d6 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -1,15 +1,19 @@ storeManager = $storeManager; $this->resource = $resourceConnection; @@ -72,6 +83,8 @@ public function __construct( $this->dateTime = $dateTime; $this->localeDate = $localeDate; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -95,12 +108,12 @@ public function build($productId) "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $specialPriceAttribute->getBackendTable()], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] )->joinLeft( ['special_from' => $specialPriceFromDate->getBackendTable()], @@ -116,7 +129,7 @@ public function build($productId) $specialPriceToDate->getAttributeId() ), '' - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.attribute_id = ?', $specialPriceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->where( @@ -127,16 +140,17 @@ public function build($productId) $currentDate )->order('t.value ' . Select::SQL_ASC) ->limit(1); - - $specialPriceDefault = clone $specialPrice; - $specialPriceDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID); - $select[] = $specialPriceDefault; + $specialPrice = $this->baseSelectProcessor->process($specialPrice); if (!$this->catalogHelper->isPriceGlobal()) { - $specialPrice->where('t.store_id = ?', $this->storeManager->getStore()->getId()); - $select[] = $specialPrice; + $priceSelectStore = clone $specialPrice; + $priceSelectStore->where('t.store_id = ?', $this->storeManager->getStore()->getId()); + $selects[] = $priceSelectStore; } - return $select; + $specialPrice->where('t.store_id = ?', Store::DEFAULT_STORE_ID); + $selects[] = $specialPrice; + + return $selects; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index d2d6d89c0a2a5..b884d2504a47a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -1,12 +1,12 @@ storeManager = $storeManager; $this->resource = $resourceConnection; $this->customerSession = $customerSession; $this->catalogHelper = $catalogHelper; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -77,28 +86,29 @@ public function build($productId) "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $this->resource->getTableName('catalog_product_entity_tier_price')], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.qty = ?', 1) ->order('t.value ' . Select::SQL_ASC) ->limit(1); - - $priceSelectDefault = clone $priceSelect; - $priceSelectDefault->where('t.website_id = ?', self::DEFAULT_WEBSITE_ID); - $select[] = $priceSelectDefault; + $priceSelect = $this->baseSelectProcessor->process($priceSelect); if (!$this->catalogHelper->isPriceGlobal()) { - $priceSelect->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()); - $select[] = $priceSelect; + $priceSelectStore = clone $priceSelect; + $priceSelectStore->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()); + $selects[] = $priceSelectStore; } - return $select; + $priceSelect->where('t.website_id = ?', self::DEFAULT_WEBSITE_ID); + $selects[] = $priceSelect; + + return $selects; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php index f3dc225685985..1ba05e55fe2b1 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php @@ -1,6 +1,6 @@ linkedProductSelectBuilder as $productSelectBuilder) { - $select = array_merge($select, $productSelectBuilder->build($productId)); + $selects = array_merge($selects, $productSelectBuilder->build($productId)); } - return $select; + return $selects; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php index e437791fd3d99..94bdaeaedf552 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php @@ -1,6 +1,6 @@ getTable('catalog_product_option_title'); foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); - $existInDefaultStore = $this->getColFromOptionTable( - $titleTableName, - (int)$object->getId(), - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ); + $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInCurrentStore : + $this->getColFromOptionTable( + $titleTableName, + (int)$object->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + if ($object->getTitle()) { + $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_title'); if ($existInCurrentStore) { - if ($object->getStoreId() == $storeId) { + if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + $connection->delete($titleTableName, ['option_title_id = ?' => $existInCurrentStore]); + + } elseif ($object->getStoreId() == $storeId) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject(['title' => $object->getTitle()]), $titleTableName @@ -270,8 +277,13 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje } } else { // we should insert record into not default store only of if it does not exist in default store - if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) - || ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInCurrentStore) + if ( + ($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + !$existInCurrentStore && + !$isDeleteStoreTitle + ) ) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Collection.php index 2d50ead9d56d9..8f434995de9e8 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Collection.php @@ -1,12 +1,13 @@ _optionValueCollectionFactory = $optionValueCollectionFactory; $this->_storeManager = $storeManager; + $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\MetadataPool::class); parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); } @@ -248,7 +253,7 @@ protected function _initSelect() ['cpe' => $this->getTable('catalog_product_entity')], sprintf( 'cpe.%s = main_table.product_id', - $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField() + $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField() ), [] ); @@ -318,18 +323,6 @@ public function reset() return $this->_reset(); } - /** - * @return \Magento\Framework\EntityManager\MetadataPool - */ - private function getMetadataPool() - { - if (null === $this->metadataPool) { - $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\MetadataPool::class); - } - return $this->metadataPool; - } - /** * @return JoinProcessorInterface */ diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 39a67f9722e18..2dad5d0a441ec 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -1,6 +1,6 @@ getTable('catalog_product_option_type_price'); + $formattedPrice = $this->getLocaleFormatter()->getNumber($object->getPrice()); - $price = (double)sprintf('%F', $object->getPrice()); + $price = (double)sprintf('%F', $formattedPrice); $priceType = $object->getPriceType(); if ($object->getPrice() && $priceType) { @@ -231,6 +237,11 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje ); $optionTypeId = $this->getConnection()->fetchOne($select); $existInCurrentStore = $this->getOptionIdFromOptionTable($titleTable, (int)$object->getId(), (int)$storeId); + + if ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && $object->getData('is_delete_store_title')) { + $object->unsetData('title'); + } + if ($object->getTitle()) { if ($existInCurrentStore) { if ($storeId == $object->getStoreId()) { @@ -410,4 +421,19 @@ public function duplicate(\Magento\Catalog\Model\Product\Option\Value $object, $ return $object; } + + /** + * Get FormatInterface to convert price from string to number format + * + * @return \Magento\Framework\Locale\FormatInterface + * @deprecated + */ + private function getLocaleFormatter() + { + if ($this->localeFormat === null) { + $this->localeFormat = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Locale\FormatInterface::class); + } + return $this->localeFormat; + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value/Collection.php index 0924808a7dcac..1f65ec334dee4 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value/Collection.php @@ -1,6 +1,6 @@ attributeResource = $attributeResource; + $this->attributeRepository = $attributeRepository; + $this->productIdLocator = $productIdLocator; + $this->metadataPool = $metadataPool; + } + + /** + * {@inheritdoc} + */ + public function get(array $skus) + { + $ids = $this->retrieveAffectedIds($skus); + $priceTable = $this->attributeResource->getTable($this->priceTable); + $dateTimeTable = $this->attributeResource->getTable($this->datetimeTable); + $linkField = $this->getEntityLinkField(); + $select = $this->attributeResource->getConnection() + ->select() + ->from( + $priceTable, + [ + 'value_id', + 'store_id', + $this->getEntityLinkField(), + 'value', + ] + ) + ->joinLeft( + $dateTimeTable . ' AS datetime_from', + $priceTable . '.' . $linkField . '=' . 'datetime_from.' . $linkField + . ' AND datetime_from.attribute_id=' . $this->getPriceFromAttributeId(), + 'value AS price_from' + ) + ->joinLeft( + $dateTimeTable . ' AS datetime_to', + $priceTable . '.' . $linkField . '=' . 'datetime_to.' . $linkField + . ' AND datetime_to.attribute_id=' . $this->getPriceToAttributeId(), + 'value AS price_to' + ) + ->where($priceTable . '.' . $linkField . ' IN (?)', $ids) + ->where($priceTable . '.attribute_id = ?', $this->getPriceAttributeId()); + + return $this->attributeResource->getConnection()->fetchAll($select); + } + + /** + * {@inheritdoc} + */ + public function update(array $prices) + { + $formattedPrices = []; + $formattedDates = []; + + /** @var \Magento\Catalog\Api\Data\SpecialPriceInterface $price */ + foreach ($prices as $price) { + $productIdsBySku = $this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()]); + $ids = array_keys($productIdsBySku[$price->getSku()]); + foreach ($ids as $id) { + $formattedPrices[] = [ + 'store_id' => $price->getStoreId(), + $this->getEntityLinkField() => $id, + 'value' => $price->getPrice(), + 'attribute_id' => $this->getPriceAttributeId(), + ]; + if ($price->getPriceFrom()) { + $formattedDates[] = [ + 'store_id' => $price->getStoreId(), + $this->getEntityLinkField() => $id, + 'value' => $price->getPriceFrom(), + 'attribute_id' => $this->getPriceFromAttributeId(), + ]; + } + if ($price->getPriceTo()) { + $formattedDates[] = [ + 'store_id' => $price->getStoreId(), + $this->getEntityLinkField() => $id, + 'value' => $price->getPriceTo(), + 'attribute_id' => $this->getPriceToAttributeId(), + ]; + } + } + } + $connection = $this->attributeResource->getConnection(); + $connection->beginTransaction(); + + try { + $this->updateItems($formattedPrices, $this->priceTable); + $this->updateItems($formattedDates, $this->datetimeTable); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotSaveException( + __('Could not save Prices.'), + $e + ); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function delete(array $prices) + { + $skus = array_unique( + array_map(function ($price) { + return $price->getSku(); + }, $prices) + ); + $ids = $this->retrieveAffectedIds($skus); + $connection = $this->attributeResource->getConnection(); + $connection->beginTransaction(); + try { + foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) { + $this->attributeResource->getConnection()->delete( + $this->attributeResource->getTable($this->priceTable), + [ + 'attribute_id = ?' => $this->getPriceAttributeId(), + $this->getEntityLinkField() . ' IN (?)' => $idsBunch + ] + ); + } + foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) { + $this->attributeResource->getConnection()->delete( + $this->attributeResource->getTable($this->datetimeTable), + [ + 'attribute_id IN (?)' => [$this->getPriceFromAttributeId(), $this->getPriceToAttributeId()], + $this->getEntityLinkField() . ' IN (?)' => $idsBunch + ] + ); + } + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\CouldNotDeleteException( + __('Could not delete Prices'), + $e + ); + } + + return true; + } + + /** + * Get link field. + * + * @return string + */ + public function getEntityLinkField() + { + return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + } + + /** + * Update items in database. + * + * @param array $items + * @param string $table + * @return void + */ + private function updateItems(array $items, $table) + { + foreach (array_chunk($items, $this->itemsPerOperation) as $itemsBunch) { + $this->attributeResource->getConnection()->insertOnDuplicate( + $this->attributeResource->getTable($table), + $itemsBunch, + ['value'] + ); + } + } + + /** + * Get special price attribute ID. + * + * @return int + */ + private function getPriceAttributeId() + { + if (!$this->priceAttributeId) { + $this->priceAttributeId = $this->attributeRepository->get('special_price')->getAttributeId(); + } + + return $this->priceAttributeId; + } + + /** + * Get special price from attribute ID. + * + * @return int + */ + private function getPriceFromAttributeId() + { + if (!$this->priceFromAttributeId) { + $this->priceFromAttributeId = $this->attributeRepository->get('special_from_date')->getAttributeId(); + } + + return $this->priceFromAttributeId; + } + + /** + * Get special price to attribute ID. + * + * @return int + */ + private function getPriceToAttributeId() + { + if (!$this->priceToAttributeId) { + $this->priceToAttributeId = $this->attributeRepository->get('special_to_date')->getAttributeId(); + } + + return $this->priceToAttributeId; + } + + /** + * Retrieve IDs of products, that were affected during price update. + * + * @param array $skus + * @return array + */ + private function retrieveAffectedIds(array $skus) + { + $affectedIds = []; + + foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) { + $affectedIds = array_merge($affectedIds, array_keys($productIds)); + } + + return array_unique($affectedIds); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Relation.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Relation.php index c057ff91e6944..013adfcb18f4a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Relation.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Relation.php @@ -1,6 +1,6 @@ eavConfig = $eavConfig; + $this->metadataPool = $metadataPool; + $this->storeResolver = $storeResolver; + } + + /** + * @param Select $select + * @return Select + */ + public function process(Select $select) + { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $statusAttribute = $this->eavConfig->getAttribute(Product::ENTITY, ProductInterface::STATUS); + + $select->joinLeft( + ['status_global_attr' => $statusAttribute->getBackendTable()], + "status_global_attr.{$linkField} = " . self::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . ' AND status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + ); + + $select->joinLeft( + ['status_attr' => $statusAttribute->getBackendTable()], + "status_attr.{$linkField} = " . self::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . ' AND status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_attr.store_id = ' . $this->storeResolver->getCurrentStoreId(), + [] + ); + + $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_ENABLED); + + return $select; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php index 69d58ddefb700..b355c9e6374bb 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php @@ -1,6 +1,6 @@ metadataPool = $metadataPool; + $this->resource = $resource; + $this->storeManager = $storeManager; + } + + /** + * Joins website-product relation table to filter products that are only in current website + * + * {@inheritdoc} + */ + public function process(Select $select) + { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $select->joinInner( + ['pw' => $this->resource->getTableName('catalog_product_website')], + 'pw.product_id = ' . BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField + . ' AND pw.website_id = ' . $this->storeManager->getWebsite()->getId(), + [] + ); + + return $select; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Setup/PropertyMapper.php b/app/code/Magento/Catalog/Model/ResourceModel/Setup/PropertyMapper.php index 01f0238a03bcc..9afc57845e1b0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Setup/PropertyMapper.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Setup/PropertyMapper.php @@ -2,7 +2,7 @@ /** * Catalog attribute property mapper * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\ResourceModel\Setup; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Url.php b/app/code/Magento/Catalog/Model/ResourceModel/Url.php index cc7daa73ea2a6..89443b0fbe8c2 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Url.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Url.php @@ -1,6 +1,6 @@ resource = $appResource; } + /** + * Get instance of ScopePool + * + * @return \Magento\Framework\App\Config + * @deprecated + */ + private function getAppConfig() + { + if ($this->appConfig === null) { + $this->appConfig = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Config::class + ); + } + return $this->appConfig; + } + /** * Check url rewrite suffix - whether we can support it * @@ -103,6 +124,24 @@ public function afterSave() return parent::afterSave(); } + /** + * {@inheritdoc} + */ + public function afterDeleteCommit() + { + if ($this->isValueChanged()) { + $this->updateSuffixForUrlRewrites(); + if ($this->isCategorySuffixChanged()) { + $this->cacheTypeList->invalidate([ + \Magento\Framework\App\Cache\Type\Block::TYPE_IDENTIFIER, + \Magento\Framework\App\Cache\Type\Collection::TYPE_IDENTIFIER + ]); + } + } + + return parent::afterDeleteCommit(); + } + /** * Check is category suffix changed * @@ -135,7 +174,12 @@ protected function updateSuffixForUrlRewrites() } $entities = $this->urlFinder->findAllByData($dataFilter); $oldSuffixPattern = '~' . preg_quote($this->getOldValue()) . '$~'; - $suffix = $this->getValue(); + if ($this->getValue() !== null) { + $suffix = $this->getValue(); + } else { + $this->getAppConfig()->clean(); + $suffix = $this->_config->getValue($this->getPath()); + } foreach ($entities as $urlRewrite) { $bind = $urlRewrite->getIsAutogenerated() ? [UrlRewrite::REQUEST_PATH => preg_replace($oldSuffixPattern, $suffix, $urlRewrite->getRequestPath())] diff --git a/app/code/Magento/Catalog/Model/System/Config/Source/Inputtype.php b/app/code/Magento/Catalog/Model/System/Config/Source/Inputtype.php index 6202ffc1a4b51..d4cc7488d9235 100644 --- a/app/code/Magento/Catalog/Model/System/Config/Source/Inputtype.php +++ b/app/code/Magento/Catalog/Model/System/Config/Source/Inputtype.php @@ -1,6 +1,6 @@ mediaConfig = $mediaConfig; + $this->context = $context; + $this->filePath = $filePath; + $this->miscParams = $miscParams; + $this->encryptor = $encryptor; + } + + /** + * {@inheritdoc} + */ + public function getUrl() + { + return $this->context->getBaseUrl() . $this->getRelativePath(DIRECTORY_SEPARATOR); + } + + /** + * {@inheritdoc} + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->getAbsolutePath($this->context->getPath()); + } + + /** + * Subroutine for building path + * + * @param string $path + * @param string $item + * @return string + */ + private function join($path, $item) + { + return trim( + $path . ($item ? DIRECTORY_SEPARATOR . ltrim($item, DIRECTORY_SEPARATOR) : ''), + DIRECTORY_SEPARATOR + ); + } + + /** + * {@inheritdoc} + */ + public function getSourceFile() + { + return $this->mediaConfig->getBaseMediaPath() + . DIRECTORY_SEPARATOR . ltrim($this->filePath, DIRECTORY_SEPARATOR); + } + + /** + * Get source content type + * + * @return string + */ + public function getSourceContentType() + { + return $this->contentType; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getFilePath() + { + return $this->filePath; + } + + /** + * {@inheritdoc} + * @return ContextInterface + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function getModule() + { + return 'cache'; + } + + /** + * Retrieve part of path based on misc params + * + * @return string + */ + private function getMiscPath() + { + return $this->encryptor->hash(implode('_', $this->miscParams), Encryptor::HASH_VERSION_MD5); + } + + /** + * Generate absolute path + * + * @param string $result + * @return string + */ + private function getAbsolutePath($result) + { + $prefix = (substr($result, 0, 1) == DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : ''; + $result = $this->join($result, $this->getModule()); + $result = $this->join($result, $this->getMiscPath()); + $result = $this->join($result, $this->getFilePath()); + return $prefix . $result; + } + + /** + * Generate relative path + * + * @param string $result + * @return string + */ + private function getRelativePath($result) + { + $result = $this->join($result, $this->getModule()); + $result = $this->join($result, $this->getMiscPath()); + $result = $this->join($result, $this->getFilePath()); + return DIRECTORY_SEPARATOR . $result; + } +} diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php new file mode 100644 index 0000000000000..f72447c6ca2c2 --- /dev/null +++ b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php @@ -0,0 +1,59 @@ +mediaConfig = $mediaConfig; + $this->filesystem = $filesystem; + $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $this->mediaDirectory->create($this->mediaConfig->getBaseMediaPath()); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getBaseMediaPath()); + } + + /** + * {@inheritdoc} + */ + public function getBaseUrl() + { + return $this->mediaConfig->getBaseMediaUrl(); + } +} diff --git a/app/code/Magento/Catalog/Model/View/Asset/Placeholder.php b/app/code/Magento/Catalog/Model/View/Asset/Placeholder.php new file mode 100644 index 0000000000000..7fa55b27a4b99 --- /dev/null +++ b/app/code/Magento/Catalog/Model/View/Asset/Placeholder.php @@ -0,0 +1,181 @@ +context = $context; + $this->scopeConfig = $scopeConfig; + $this->assetRepo = $assetRepo; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function getUrl() + { + if ($this->getFilePath() !== null) { + $result = $this->context->getBaseUrl() . '/' . $this->getModule() . '/' . $this->getFilePath(); + } else { + $result = $this->assetRepo->getUrl("Magento_Catalog::images/product/placeholder/{$this->type}.jpg"); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + if ($this->getFilePath() !== null) { + $result = $this->getContext()->getPath() + . DIRECTORY_SEPARATOR . $this->getModule() + . DIRECTORY_SEPARATOR . $this->getFilePath(); + } else { + $defaultPlaceholder = $this->assetRepo->createAsset( + "Magento_Catalog::images/product/placeholder/{$this->type}.jpg" + ); + try { + $result = $defaultPlaceholder->getSourceFile(); + } catch (NotFoundException $e) { + $result = null; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getSourceFile() + { + return $this->getPath(); + } + + /** + * Get source content type + * + * @return string + */ + public function getSourceContentType() + { + return $this->contentType; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getFilePath() + { + if ($this->filePath !== null) { + return $this->filePath; + } + // check if placeholder defined in config + $isConfigPlaceholder = $this->scopeConfig->getValue( + "catalog/placeholder/{$this->type}_placeholder", + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $this->filePath = $isConfigPlaceholder; + + return $this->filePath; + } + + /** + * {@inheritdoc} + * @return ContextInterface + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function getModule() + { + return 'placeholder'; + } +} diff --git a/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/Date.php index 779c725bd909e..aeeaf049556d4 100644 --- a/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/Date.php @@ -1,6 +1,6 @@ localeDate = $localeDate; + } + + /** + * Set the current date to Special Price From attribute if it empty + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + /** @var $product \Magento\Catalog\Model\Product */ + $product = $observer->getEvent()->getProduct(); + if ($product->getSpecialPrice() && !$product->getSpecialFromDate()) { + $product->setData('special_from_date', $this->localeDate->date()); + } + + return $this; + } +} diff --git a/app/code/Magento/Catalog/Observer/SwitchPriceAttributeScopeOnConfigChange.php b/app/code/Magento/Catalog/Observer/SwitchPriceAttributeScopeOnConfigChange.php new file mode 100644 index 0000000000000..562c76830d4e6 --- /dev/null +++ b/app/code/Magento/Catalog/Observer/SwitchPriceAttributeScopeOnConfigChange.php @@ -0,0 +1,78 @@ +config = $config; + $this->productAttributeRepository = $productAttributeRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * Change scope for all price attributes according to + * 'Catalog Price Scope' configuration parameter value + * + * @param EventObserver $observer + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute(EventObserver $observer) + { + $this->searchCriteriaBuilder->addFilter('frontend_input', 'price'); + $criteria = $this->searchCriteriaBuilder->create(); + + $scope = $this->config->getValue(Store::XML_PATH_PRICE_SCOPE); + $scope = ($scope == Store::PRICE_SCOPE_WEBSITE) + ? ProductAttributeInterface::SCOPE_WEBSITE_TEXT + : ProductAttributeInterface::SCOPE_GLOBAL_TEXT; + + $priceAttributes = $this->productAttributeRepository->getList($criteria)->getItems(); + + /** @var ProductAttributeInterface $priceAttribute */ + foreach ($priceAttributes as $priceAttribute) { + $priceAttribute->setScope($scope); + $this->productAttributeRepository->save($priceAttribute); + } + } +} diff --git a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php index 72bf4468080e3..04b2dadf3b6f4 100644 --- a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php +++ b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php @@ -1,6 +1,6 @@ cache = $cache; $this->isCacheEnabled = $cacheState->isEnabled(\Magento\Eav\Model\Cache\Type::TYPE_IDENTIFIER); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); } /** @@ -43,12 +54,12 @@ public function aroundGetAttributesUsedInListing( ) { $cacheId = self::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $config->getEntityTypeId() . '_' . $config->getStoreId(); if ($this->isCacheEnabled && ($attributes = $this->cache->load($cacheId))) { - return unserialize($attributes); + return $this->serializer->unserialize($attributes); } $attributes = $proceed(); if ($this->isCacheEnabled) { $this->cache->save( - serialize($attributes), + $this->serializer->serialize($attributes), $cacheId, [ \Magento\Eav\Model\Cache\Type::CACHE_TAG, @@ -71,12 +82,12 @@ public function aroundGetAttributesUsedForSortBy( $cacheId = self::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $config->getEntityTypeId() . '_' . $config->getStoreId(); if ($this->isCacheEnabled && ($attributes = $this->cache->load($cacheId))) { - return unserialize($attributes); + return $this->serializer->unserialize($attributes); } $attributes = $proceed(); if ($this->isCacheEnabled) { $this->cache->save( - serialize($attributes), + $this->serializer->serialize($attributes), $cacheId, [ \Magento\Eav\Model\Cache\Type::CACHE_TAG, diff --git a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php index b32d3f14517c8..82bee56cd9ef8 100644 --- a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php @@ -1,6 +1,6 @@ calculator = $calculator; + } + + /** + * Get raw value of "as low as" as a minimal among tier prices + * {@inheritdoc} + */ + public function getValue(SaleableInterface $saleableItem) + { + /** @var TierPrice $price */ + $price = $saleableItem->getPriceInfo()->getPrice(TierPrice::PRICE_CODE); + $tierPriceList = $price->getTierPriceList(); + + $tierPrices = []; + foreach ($tierPriceList as $tierPrice) { + /** @var AmountInterface $price */ + $price = $tierPrice['price']; + $tierPrices[] = $price->getValue(); + } + + return $tierPrices ? min($tierPrices) : null; + } + + /** + * Return calculated amount object that keeps "as low as" value + * {@inheritdoc} + */ + public function getAmount(SaleableInterface $saleableItem) + { + $value = $this->getValue($saleableItem); + + return $value === null + ? null + : $this->calculator->getAmount($value, $saleableItem); + } +} diff --git a/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php b/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php index bac187bb820fe..4187a9a208b09 100644 --- a/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php @@ -1,6 +1,6 @@ salableResolver = $salableResolver ?: ObjectManager::getInstance()->get(SalableResolverInterface::class); + $this->minimalPriceCalculator = $minimalPriceCalculator + ?: ObjectManager::getInstance()->get(MinimalPriceCalculatorInterface::class); + } + /** * @return string */ protected function _toHtml() { - if (!$this->getSaleableItem() || $this->getSaleableItem()->getCanShowPrice() === false) { + if (!$this->salableResolver->isSalable($this->getSaleableItem())) { return ''; } $result = parent::_toHtml(); - - try { - /** @var MsrpPrice $msrpPriceType */ - $msrpPriceType = $this->getSaleableItem()->getPriceInfo()->getPrice('msrp_price'); - } catch (\InvalidArgumentException $e) { - $this->_logger->critical($e); - return $this->wrapResult($result); - } - //Renders MSRP in case it is enabled - $product = $this->getSaleableItem(); - if ($msrpPriceType->canApplyMsrp($product) && $msrpPriceType->isMinimalPriceLessMsrp($product)) { + if ($this->isMsrpPriceApplicable()) { /** @var BasePriceBox $msrpBlock */ $msrpBlock = $this->rendererPool->createPriceRender( MsrpPrice::PRICE_CODE, @@ -56,6 +86,25 @@ protected function _toHtml() return $this->wrapResult($result); } + /** + * Check is MSRP applicable for the current product. + * + * @return bool + */ + protected function isMsrpPriceApplicable() + { + try { + /** @var MsrpPrice $msrpPriceType */ + $msrpPriceType = $this->getSaleableItem()->getPriceInfo()->getPrice('msrp_price'); + } catch (\InvalidArgumentException $e) { + $this->_logger->critical($e); + return false; + } + + $product = $this->getSaleableItem(); + return $msrpPriceType->canApplyMsrp($product) && $msrpPriceType->isMinimalPriceLessMsrp($product); + } + /** * Wrap with standard required container * @@ -77,11 +126,15 @@ protected function wrapResult($html) */ public function renderAmountMinimal() { - /** @var \Magento\Catalog\Pricing\Price\FinalPrice $price */ - $price = $this->getPriceType(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE); $id = $this->getPriceId() ? $this->getPriceId() : 'product-minimal-price-' . $this->getSaleableItem()->getId(); + + $amount = $this->minimalPriceCalculator->getAmount($this->getSaleableItem()); + if ($amount === null) { + return ''; + } + return $this->renderAmount( - $price->getMinimalPrice(), + $amount, [ 'display_label' => __('As low as'), 'price_id' => $id, @@ -110,13 +163,15 @@ public function hasSpecialPrice() */ public function showMinimalPrice() { + $minTierPrice = $this->minimalPriceCalculator->getValue($this->getSaleableItem()); + /** @var Price\FinalPrice $finalPrice */ $finalPrice = $this->getPriceType(Price\FinalPrice::PRICE_CODE); $finalPriceValue = $finalPrice->getAmount()->getValue(); - $minimalPriceAValue = $finalPrice->getMinimalPrice()->getValue(); + return $this->getDisplayMinimalPrice() - && $minimalPriceAValue - && $minimalPriceAValue < $finalPriceValue; + && $minTierPrice !== null + && $minTierPrice < $finalPriceValue; } /** diff --git a/app/code/Magento/Catalog/Pricing/Render/PriceBox.php b/app/code/Magento/Catalog/Pricing/Render/PriceBox.php index 92ff22decb1d0..156fbb8633699 100644 --- a/app/code/Magento/Catalog/Pricing/Render/PriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/PriceBox.php @@ -1,6 +1,6 @@ getConnection() ->createTable($table); + $customerGroupTable = $setup->getConnection()->describeTable($setup->getTable('customer_group')); + $customerGroupIdType = $customerGroupTable['customer_group_id']['DATA_TYPE'] == 'int' + ? \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER : $customerGroupTable['customer_group_id']['DATA_TYPE']; /** * Create table 'catalog_product_entity_tier_price' */ @@ -1883,7 +1887,7 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Customer Group ID' @@ -2426,7 +2430,6 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con 'option_id', $installer->getTable('catalog_product_option'), 'option_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE, \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE ) ->setComment( @@ -2937,7 +2940,7 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group ID' @@ -3056,7 +3059,7 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group ID' diff --git a/app/code/Magento/Catalog/Setup/Recurring.php b/app/code/Magento/Catalog/Setup/Recurring.php index 85484cdddb218..e6cb302747918 100644 --- a/app/code/Magento/Catalog/Setup/Recurring.php +++ b/app/code/Magento/Catalog/Setup/Recurring.php @@ -1,6 +1,6 @@ categorySetupFactory = $categorySetupFactory; $this->eavSetupFactory = $eavSetupFactory; + $this->attributeCache = $attributeCache; } /** @@ -135,19 +146,24 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface } if (version_compare($context->getVersion(), '2.0.4') < 0) { + $mediaBackendType = 'static'; + $mediaBackendModel = null; /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); $categorySetup->updateAttribute( 'catalog_product', 'media_gallery', 'backend_type', - 'static' + $mediaBackendType ); $categorySetup->updateAttribute( 'catalog_product', 'media_gallery', - 'backend_model' + 'backend_model', + $mediaBackendModel ); + + $this->changeMediaGalleryAttributeInCache($mediaBackendType, $mediaBackendModel); } if (version_compare($context->getVersion(), '2.0.5', '<')) { @@ -340,10 +356,10 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface ] ); } - + if (version_compare($context->getVersion(), '2.0.7') < 0) { - /** @var EavSetup $eavSetupF */ - $eavSetup= $this->eavSetupFactory->create(['setup' => $setup]); + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $eavSetup->updateAttribute( ProductAttributeInterface::ENTITY_TYPE_CODE, @@ -353,7 +369,53 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface ] ); } - + + if (version_compare($context->getVersion(), '2.1.3') < 0) { + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + $this->changePriceAttributeDefaultScope($categorySetup); + } + $setup->endSetup(); } + + /** + * @param \Magento\Catalog\Setup\CategorySetup $categorySetup + * @return void + */ + private function changePriceAttributeDefaultScope($categorySetup) + { + $entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); + foreach (['price', 'cost', 'special_price'] as $attributeCode) { + $attribute = $categorySetup->getAttribute($entityTypeId, $attributeCode); + $categorySetup->updateAttribute( + $entityTypeId, + $attribute['attribute_id'], + 'is_global', + \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL + ); + + } + } + + /** + * @param string $mediaBackendType + * @param string $mediaBackendModel + * @return void + */ + private function changeMediaGalleryAttributeInCache($mediaBackendType, $mediaBackendModel) + { + // need to do, because media_gallery has backend model in cache. + $catalogProductAttributes = $this->attributeCache->getAttributes('catalog_product', '0-0'); + + if (is_array($catalogProductAttributes)) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $catalogProductAttribute */ + foreach ($catalogProductAttributes as $catalogProductAttribute) { + if ($catalogProductAttribute->getAttributeCode() == 'media_gallery') { + $catalogProductAttribute->setBackendModel($mediaBackendModel); + $catalogProductAttribute->setBackendType($mediaBackendType); + } + } + } + } } diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php old mode 100644 new mode 100755 index 9683632f121b6..db0aaa3a010e3 --- a/app/code/Magento/Catalog/Setup/UpgradeSchema.php +++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php @@ -1,6 +1,6 @@ addUniqueKeyToCategoryProductTable($setup); } - if (version_compare($context->getVersion(), '2.1.0', '<')) { + if (version_compare($context->getVersion(), '2.1.4', '<')) { $this->addPercentageValueColumn($setup); + $tables = [ + 'catalog_product_index_price_cfg_opt_agr_idx', + 'catalog_product_index_price_cfg_opt_agr_tmp', + 'catalog_product_index_price_cfg_opt_idx', + 'catalog_product_index_price_cfg_opt_tmp', + 'catalog_product_index_price_final_idx', + 'catalog_product_index_price_final_tmp', + 'catalog_product_index_price_idx', + 'catalog_product_index_price_opt_agr_idx', + 'catalog_product_index_price_opt_agr_tmp', + 'catalog_product_index_price_opt_idx', + 'catalog_product_index_price_opt_tmp', + 'catalog_product_index_price_tmp', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer', 'nullable' => false] + ); + } + $this->addSourceEntityIdToProductEavIndex($setup); + $this->recreateCatalogCategoryProductIndexTmpTable($setup); } + $setup->endSetup(); } + /** + * Add the column 'source_id' to the Product EAV index tables. + * It allows to identify which entity was used to create value in the index. + * It is useful to identify original entity in a composite products. + * + * @param SchemaSetupInterface $setup + * @return void + */ + private function addSourceEntityIdToProductEavIndex(SchemaSetupInterface $setup) + { + $tables = [ + 'catalog_product_index_eav', + 'catalog_product_index_eav_idx', + 'catalog_product_index_eav_tmp', + 'catalog_product_index_eav_decimal', + 'catalog_product_index_eav_decimal_idx', + 'catalog_product_index_eav_decimal_tmp', + ]; + $connection = $setup->getConnection(); + foreach ($tables as $tableName) { + $tableName = $setup->getTable($tableName); + if (!$connection->tableColumnExists($tableName, 'source_id')) { + $connection->addColumn( + $tableName, + 'source_id', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => 0, + 'comment' => 'Original entity Id for attribute value', + ] + ); + $connection->dropIndex($tableName, $connection->getPrimaryKeyName($tableName)); + $primaryKeyFields = ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id']; + $setup->getConnection()->addIndex( + $tableName, + $connection->getIndexName($tableName, $primaryKeyFields), + $primaryKeyFields, + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY + ); + } + } + } + /** * @param SchemaSetupInterface $setup * @return void @@ -291,16 +360,90 @@ private function removeGroupPrice(SchemaSetupInterface $setup) private function addPercentageValueColumn(SchemaSetupInterface $setup) { $connection = $setup->getConnection(); - $connection->addColumn( - $setup->getTable('catalog_product_entity_tier_price'), - 'percentage_value', - [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - 'nullable' => true, - 'length' => '5,2', - 'comment' => 'Percentage value', - 'after' => 'value' - ] - ); + $tableName = $setup->getTable('catalog_product_entity_tier_price'); + + if (!$connection->tableColumnExists($tableName, 'percentage_value')) { + $connection->addColumn( + $tableName, + 'percentage_value', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, + 'nullable' => true, + 'length' => '5,2', + 'comment' => 'Percentage value', + 'after' => 'value' + ] + ); + } + } + + /** + * Drop and recreate catalog_category_product_index_tmp table + * + * Before this update the catalog_category_product_index_tmp table was created without usage of PK + * and with engine=MEMORY. Such structure of catalog_category_product_index_tmp table causes + * issues with MySQL DB replication. + * + * To avoid replication issues this method drops catalog_category_product_index_tmp table + * and creates new one with PK and engine=InnoDB + * + * @param SchemaSetupInterface $setup + * @return void + */ + private function recreateCatalogCategoryProductIndexTmpTable(SchemaSetupInterface $setup) + { + $tableName = $setup->getTable('catalog_category_product_index_tmp'); + + // Drop catalog_category_product_index_tmp table + $setup->getConnection()->dropTable($tableName); + + // Create catalog_category_product_index_tmp table with PK and engine=InnoDB + $table = $setup->getConnection() + ->newTable($tableName) + ->addColumn( + 'category_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'primary' => true, 'default' => '0'], + 'Category ID' + ) + ->addColumn( + 'product_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'primary' => true, 'default' => '0'], + 'Product ID' + ) + ->addColumn( + 'position', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['nullable' => false, 'default' => '0'], + 'Position' + ) + ->addColumn( + 'is_parent', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Is Parent' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'primary' => true, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'visibility', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false], + 'Visibility' + ) + ->setComment('Catalog Category Product Indexer temporary table'); + + $setup->getConnection()->createTable($table); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Category/AbstractCategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Category/AbstractCategoryTest.php index cb7d1c20b3356..f22f7f50859a2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Category/AbstractCategoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Category/AbstractCategoryTest.php @@ -1,6 +1,6 @@ context = $this->getMockBuilder(\Magento\Backend\Block\Context::class) @@ -53,13 +56,7 @@ public function testToHtml() ->disableOriginalConstructor() ->setMethods(['dispatch']) ->getMock(); - $eventManager->expects($this->once())->method('dispatch')->will($this->returnValue(true)); - - $scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config::class) - ->setMethods(['getValue']) - ->disableOriginalConstructor()->getMock(); - $scopeConfig->expects($this->once())->method('getValue')->withAnyParameters() - ->will($this->returnValue(false)); + $eventManager->expects($this->exactly(2))->method('dispatch')->will($this->returnValue(true)); $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor() ->setMethods(['setStoreId', 'load', 'getId', '__wakeup', '__sleep']) @@ -93,8 +90,6 @@ public function testToHtml() $this->context->expects($this->once())->method('getEventManager') ->will($this->returnValue($eventManager)); - $this->context->expects($this->once())->method('getScopeConfig') - ->will($this->returnValue($scopeConfig)); $this->context->expects($this->once())->method('getLayout') ->will($this->returnValue($layout)); $this->context->expects($this->once())->method('getRequest') diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/Grid/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/Grid/LinkTest.php index 8bc6675933768..11efb045a1c2f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/Grid/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/Grid/LinkTest.php @@ -1,6 +1,6 @@ productMock->expects($this->once()) ->method('getIdentities') @@ -154,9 +154,9 @@ public function testGetAddToCartPostParams() ]; $this->typeInstanceMock->expects($this->once()) - ->method('hasRequiredOptions') + ->method('isPossibleBuyFromList') ->with($this->equalTo($this->productMock)) - ->will($this->returnValue(false)); + ->will($this->returnValue(true)); $this->cartHelperMock->expects($this->any()) ->method('getAddUrl') ->with($this->equalTo($this->productMock), $this->equalTo([])) diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListTest.php index 786185e60e9db..de0e3d5f0d5ef 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListTest.php @@ -1,6 +1,6 @@ method('init') ->willReturnMap([ [$productMock, 'product_page_image_small', [], $this->imageHelper], - [$productMock, 'product_page_image_medium', [], $this->imageHelper], - [$productMock, 'product_page_image_large', [], $this->imageHelper], + [$productMock, 'product_page_image_medium_no_frame', [], $this->imageHelper], + [$productMock, 'product_page_image_large_no_frame', [], $this->imageHelper], ]) ->willReturnSelf(); $this->imageHelper->expects($this->exactly(3)) @@ -129,19 +129,6 @@ public function testGetGalleryImages() ->method('getUrl') ->willReturn('product_page_image_large_url'); - $this->imageHelper->expects($this->exactly(2)) - ->method('constrainOnly') - ->with(true) - ->willReturnSelf(); - $this->imageHelper->expects($this->exactly(2)) - ->method('keepAspectRatio') - ->with(true) - ->willReturnSelf(); - $this->imageHelper->expects($this->exactly(2)) - ->method('keepFrame') - ->with(false) - ->willReturnSelf(); - $images = $this->model->getGalleryImages(); $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $images); } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/OptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/OptionsTest.php index 0e8d55232221a..9bafb60fc5b44 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/OptionsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/OptionsTest.php @@ -1,6 +1,6 @@ productTypeConfig = $this->getMock(\Magento\Catalog\Model\ProductTypes\ConfigInterface::class); $this->registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); - $this->view = $helper->getObject( + $this->view = $helper->getObject( \Magento\Catalog\Block\Product\View::class, ['productTypeConfig' => $this->productTypeConfig, 'registry' => $this->registryMock] ); @@ -65,7 +65,7 @@ public function testShouldRenderQuantity() public function testGetIdentities() { - $productTags = ['catalog_product_1']; + $productTags = ['cat_p_1']; $product = $this->getMock(\Magento\Catalog\Model\Product::class, [], [], '', false); $category = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); @@ -84,6 +84,6 @@ public function testGetIdentities() ] ) ); - $this->assertEquals(['catalog_product_1', 'catalog_category_1'], $this->view->getIdentities()); + $this->assertEquals(['cat_p_1', 'cat_c_1'], $this->view->getIdentities()); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php index cfd009787158a..251eda5ce32fa 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php @@ -1,6 +1,6 @@ objectManager = new ObjectManagerHelper($this); @@ -63,7 +66,7 @@ protected function setUp() false, false ); - $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, ['getValue'], [], '', false, false); + $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, [], [], '', false, false); $this->cacheState = $this->getMock( \Magento\Framework\App\Cache\State::class, ['isEnabled'], @@ -109,6 +112,9 @@ protected function setUp() ); } + /** + * @inheritdoc + */ protected function tearDown() { $this->block = null; @@ -168,6 +174,9 @@ public function testGetCurrentPage($pageNumber, $expectedResult) $this->assertEquals($expectedResult, $this->block->getCurrentPage()); } + /** + * @return array + */ public function getCurrentPageDataProvider() { return [ @@ -184,12 +193,13 @@ public function testGetProductsCount() $this->assertEquals(2, $this->block->getProductsCount()); } + /** + * @return void + */ protected function generalGetProductCollection() { - $this->eventManager->expects($this->once())->method('dispatch') + $this->eventManager->expects($this->exactly(2))->method('dispatch') ->will($this->returnValue(true)); - $this->scopeConfig->expects($this->once())->method('getValue')->withAnyParameters() - ->willReturn(false); $this->cacheState->expects($this->atLeastOnce())->method('isEnabled')->withAnyParameters() ->willReturn(false); $this->catalogConfig->expects($this->once())->method('getProductAttributes') @@ -329,6 +339,9 @@ public function testGetProductAllCollection($pagerEnable, $productsCount, $produ ); } + /** + * @return array + */ public function getProductCollectionDataProvider() { return [ diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php index e7e8171868f94..7a58617b90d58 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Rss/CategoryTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + } + + public function executeDataProvider() + { + return [ + ['image1', 'image1'], + ['image2', 'image2'], + [null, 'image'], + ]; + } + + /** + * @param string $name + * @param string $savedName + * + * @dataProvider executeDataProvider + */ + public function testExecute($name, $savedName) + { + $request = $this->objectManager->getObject(Request::class); + + $uploader = $this->getMock(ImageUploader::class, ['saveFileToTmpDir'], [], '', false); + + $resultFactory = $this->getMock(ResultFactory::class, ['create'], [], '', false); + + $resultFactory->expects($this->once()) + ->method('create') + ->will($this->returnValue(new DataObject())); + + $model = $this->objectManager->getObject(Model::class, [ + 'request' => $request, + 'resultFactory' => $resultFactory, + 'imageUploader' => $uploader + ]); + + $uploader->expects($this->once()) + ->method('saveFileToTmpDir') + ->with($savedName) + ->will($this->returnValue([])); + + $request->setParam('param_name', $name); + + $model->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php index 51d99f7219575..6c6fa4c3a10ab 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php @@ -1,10 +1,12 @@ markTestSkipped('Due to MAGETWO-48956'); $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->contextMock = $this->getMock( - \Magento\Backend\App\Action\Context::class, - [ - 'getTitle', - 'getRequest', - 'getObjectManager', - 'getEventManager', - 'getResponse', - 'getMessageManager', - 'getResultRedirectFactory' - ], - [], - '', - false - ); $this->resultRedirectFactoryMock = $this->getMock( \Magento\Backend\Model\View\Result\RedirectFactory::class, ['create'], @@ -109,13 +74,6 @@ protected function setUp() '', false ); - $this->resultRawFactoryMock = $this->getMock( - \Magento\Framework\Controller\Result\RawFactory::class, - [], - [], - '', - false - ); $this->resultJsonFactoryMock = $this->getMock( \Magento\Framework\Controller\Result\JsonFactory::class, ['create'], @@ -151,12 +109,6 @@ protected function setUp() true, ['dispatch'] ); - $this->responseMock = $this->getMockForAbstractClass( - \Magento\Framework\App\ResponseInterface::class, - [], - '', - false - ); $this->messageManagerMock = $this->getMockForAbstractClass( \Magento\Framework\Message\ManagerInterface::class, [], @@ -167,23 +119,15 @@ protected function setUp() ['addSuccess', 'getMessages'] ); - $this->contextMock->expects($this->any())->method('getTitle')->willReturn($this->titleMock); - $this->contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->any())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any())->method('getEventManager')->willReturn($this->eventManagerMock); - $this->contextMock->expects($this->any())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->any())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($this->resultRedirectFactoryMock); - $this->save = $this->objectManager->getObject( \Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ - 'context' => $this->contextMock, - 'resultRawFactory' => $this->resultRawFactoryMock, + 'request' => $this->requestMock, + 'eventManager' => $this->eventManagerMock, + 'messageManager' => $this->messageManagerMock, 'resultJsonFactory' => $this->resultJsonFactoryMock, - 'layoutFactory' => $this->layoutFactoryMock + 'layoutFactory' => $this->layoutFactoryMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock ] ); } @@ -201,6 +145,8 @@ protected function setUp() */ public function testExecute($categoryId, $storeId, $parentId) { + $this->markTestSkipped('Due to MAGETWO-48956'); + $rootCategoryId = \Magento\Catalog\Model\Category::TREE_ROOT_ID; $products = [['any_product']]; $postData = [ @@ -577,4 +523,95 @@ public function dataProviderExecute() ] ]; } + + /** + * @return array + */ + public function imagePreprocessingDataProvider() + { + return [ + [['attribute1' => null, 'attribute2' => 123]], + [['attribute2' => 123]] + ]; + } + + /** + * @dataProvider imagePreprocessingDataProvider + * + * @param array $data + */ + public function testImagePreprocessingWithoutValue($data) + { + $eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, ['getEntityType'], [], '', false); + + $imageBackendModel = $this->objectManager->getObject( + \Magento\Catalog\Model\Category\Attribute\Backend\Image::class + ); + + $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute1', + 'backend' => $imageBackendModel + ]), + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute2', + 'backend' => new \Magento\Framework\DataObject() + ]) + ]]); + + $eavConfig->expects($this->once()) + ->method('getEntityType') + ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) + ->will($this->returnValue($collection)); + + $model = $this->objectManager->getObject(\Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ + 'eavConfig' => $eavConfig + ]); + + $result = $model->imagePreprocessing($data); + + $this->assertEquals([ + 'attribute1' => false, + 'attribute2' => 123 + ], $result); + } + + public function testImagePreprocessingWithValue() + { + $eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, ['getEntityType'], [], '', false); + + $imageBackendModel = $this->objectManager->getObject( + \Magento\Catalog\Model\Category\Attribute\Backend\Image::class + ); + + $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute1', + 'backend' => $imageBackendModel + ]), + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute2', + 'backend' => new \Magento\Framework\DataObject() + ]) + ]]); + + $eavConfig->expects($this->once()) + ->method('getEntityType') + ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) + ->will($this->returnValue($collection)); + + $model = $this->objectManager->getObject(Model::class, [ + 'eavConfig' => $eavConfig + ]); + + $result = $model->imagePreprocessing([ + 'attribute1' => 'somevalue', + 'attribute2' => null + ]); + + $this->assertEquals([ + 'attribute1' => 'somevalue', + 'attribute2' => null + ], $result); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Widget/CategoriesJsonTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Widget/CategoriesJsonTest.php index 824d1c7bf23f2..59b2238aacdbc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Widget/CategoriesJsonTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Widget/CategoriesJsonTest.php @@ -1,7 +1,7 @@ objectManager = new ObjectManager($this); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productBuilderMock = $this->getMockBuilder(ProductBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultJsonFactoryMock = $this->getMockBuilder(JsonFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->setMethods(['getParam', 'setParam']) + ->getMockForAbstractClass(); + $this->contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->attributeSetRepositoryMock = $this->getMockBuilder(AttributeSetRepositoryInterface::class) + ->setMethods(['get']) + ->getMockForAbstractClass(); + $this->attributeSetInterfaceMock = $this->getMockBuilder(AttributeSetInterface::class) + ->getMockForAbstractClass(); + $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) + ->disableOriginalConstructor() + ->setMethods(['addFilter', 'create', 'setPageSize']) + ->getMockForAbstractClass(); + $this->searchCriteriaMock = $this->getMockBuilder(SearchCriteria::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeGroupRepositoryMock = $this->getMockBuilder(AttributeGroupRepositoryInterface::class) + ->setMethods(['getList']) + ->getMockForAbstractClass(); + $this->attributeGroupSearchResultsMock = $this->getMockBuilder(AttributeGroupSearchResultsInterface::class) + ->setMethods(['getItems']) + ->getMockForAbstractClass(); + $this->attributeGroupInterfaceFactoryMock = $this->getMockBuilder(AttributeGroupInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeGroupInterfaceMock = $this->getMockBuilder(AttributeGroupInterface::class) + ->setMethods(['getExtensionAttributes']) + ->getMockForAbstractClass(); + $this->jsonMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = $this->objectManager->getObject( + AddAttributeToTemplate::class, + [ + 'context' => $this->contextMock, + 'productBuilder' => $this->productBuilderMock, + 'resultJsonFactory' => $this->resultJsonFactoryMock, + ] + ); + + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeSetRepository', + $this->attributeSetRepositoryMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'searchCriteriaBuilder', + $this->searchCriteriaBuilderMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeGroupRepository', + $this->attributeGroupRepositoryMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeGroupFactory', + $this->attributeGroupInterfaceFactoryMock + ); + } + + public function testExecuteWithoutAttributeGroupItems() + { + $groupCode = 'attributes'; + $groupName = 'Attributes'; + $groupSortOrder = '15'; + $templateId = '4'; + $attributeIds = [ + 'selected' => ["178"], + 'total' => '1' + ]; + + $this->requestMock + ->expects($this->any()) + ->method('getParam') + ->willReturnMap( + [ + ['groupCode', null, $groupCode], + ['groupName', null, $groupName], + ['groupSortOrder', null, $groupSortOrder], + ['templateId', null, $templateId], + ['attributeIds', [], $attributeIds] + ] + ); + + $this->attributeSetRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->attributeSetInterfaceMock); + + $this->searchCriteriaBuilderMock->expects($this->any()) + ->method('addFilter') + ->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->any()) + ->method('create') + ->willReturn($this->searchCriteriaMock); + $this->searchCriteriaBuilderMock->expects($this->once()) + ->method('setPageSize') + ->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->never()) + ->method('addSortOrder') + ->willReturnSelf(); + + $this->attributeGroupRepositoryMock->expects($this->once()) + ->method('getList') + ->willReturn($this->attributeGroupSearchResultsMock); + $this->attributeGroupSearchResultsMock->expects($this->once()) + ->method('getItems') + ->willReturn(null); + + $this->attributeGroupInterfaceFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->attributeGroupInterfaceMock); + $this->attributeGroupInterfaceMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willThrowException(new LocalizedException(__('Could not get extension attributes'))); + + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->jsonMock); + $this->jsonMock->expects($this->once())->method('setJsonData') + ->willReturnSelf(); + + $this->controller->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/EditTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/EditTest.php index be6cd55729922..b2eb887f04b7a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/EditTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/EditTest.php @@ -1,6 +1,6 @@ objectManager = new ObjectManager($this); - $this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class) - ->disableOriginalConstructor() - ->getMock(); $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->setMethods(['getPost']) ->getMockForAbstractClass(); - $this->storeMock = $this->getMockBuilder(StoreInterface::class) - ->setMethods(['getWebsite']) - ->getMockForAbstractClass(); - $this->websiteMock = $this->getMockBuilder(WebsiteInterface::class) - ->getMockForAbstractClass(); $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) ->getMockForAbstractClass(); - $this->dateFilterMock = $this->getMockBuilder(DateFilter::class) - ->disableOriginalConstructor() - ->getMock(); $this->stockFilterMock = $this->getMockBuilder(StockDataFilter::class) ->disableOriginalConstructor() ->getMock(); $this->productMock = $this->getMockBuilder(Product::class) - ->setMethods([ - 'setData', - 'addData', - 'getId', - 'setWebsiteIds', - 'isLockedAttribute', - 'lockAttribute', - 'getAttributes', - 'unlockAttribute', - 'getOptionsReadOnly', - 'setOptions', - 'setCanSaveCustomOptions', - '__sleep', - '__wakeup', - 'getSku', - 'getProductLinks', - 'getWebsiteIds' - ]) + ->setMethods( + [ + 'getId', + 'isLockedAttribute', + 'lockAttribute', + 'getAttributes', + 'unlockAttribute', + 'getOptionsReadOnly', + 'getSku', + 'getProductLinks', + ] + ) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $this->customOptionFactoryMock = $this->getMockBuilder(ProductCustomOptionInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->customOptionMock = $this->getMockBuilder(ProductCustomOptionInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); - $this->productLinksMock->expects($this->any()) ->method('initializeLinks') ->willReturn($this->productMock); @@ -175,10 +112,7 @@ protected function setUp() 'storeManager' => $this->storeManagerMock, 'stockFilter' => $this->stockFilterMock, 'productLinks' => $this->productLinksMock, - 'dateFilter' => $this->dateFilterMock, 'customOptionFactory' => $this->customOptionFactoryMock, - 'productLinkFactory' => $this->productLinkFactoryMock, - 'productRepository' => $this->productRepositoryMock, ]); $this->linkResolverMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Link\Resolver::class) @@ -192,22 +126,23 @@ protected function setUp() /** * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param bool $isSingleStore + * @param array $websiteIds + * @param array $expWebsiteIds + * + * @dataProvider initializeDataProvider */ - public function testInitialize() + public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) { - $this->customOptionMock->expects($this->once()) - ->method('setProductSku'); - $this->customOptionMock->expects($this->once()) - ->method('setOptionId'); - $optionsData = [ - 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1'], - 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1'], + 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1', 'option_id' => ''], + 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '13'], + 'option3' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '14'] ]; $productData = [ 'stock_data' => ['stock_data'], 'options' => $optionsData, + 'website_ids' => $websiteIds ]; $attributeNonDate = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) ->disableOriginalConstructor() @@ -224,82 +159,95 @@ public function testInitialize() ->disableOriginalConstructor() ->getMock(); - $attributeNonDate->expects($this->any()) - ->method('getBackend') - ->willReturn($attributeNonDateBackEnd); - $attributeDate->expects($this->any()) - ->method('getBackend') - ->willReturn($attributeDateBackEnd); - $this->productMock->expects($this->any()) - ->method('getProductLinks') - ->willReturn([]); - $attributeNonDateBackEnd->expects($this->any()) - ->method('getType') - ->willReturn('non-datetime'); - $attributeDateBackEnd->expects($this->any()) - ->method('getType') - ->willReturn('datetime'); - - $attributesArray = [ - $attributeNonDate, - $attributeDate - ]; + $attributeNonDate->expects($this->any())->method('getBackend')->willReturn($attributeNonDateBackEnd); + $attributeDate->expects($this->any())->method('getBackend')->willReturn($attributeDateBackEnd); + $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); + $attributeNonDateBackEnd->expects($this->any())->method('getType')->willReturn('non-datetime'); + $attributeDateBackEnd->expects($this->any())->method('getType')->willReturn('datetime'); $useDefaults = ['attributeCode1', 'attributeCode2']; - $this->requestMock->expects($this->at(0)) - ->method('getPost') - ->with('product') - ->willReturn($productData); - $this->requestMock->expects($this->at(1)) - ->method('getPost') - ->with('use_default') - ->willReturn($useDefaults); + $this->requestMock->expects($this->any())->method('getPost')->willReturnMap( + [ + ['product', [], $productData], + ['use_default', null, $useDefaults] + ] + ); $this->linkResolverMock->expects($this->once())->method('getLinks')->willReturn([]); - $this->stockFilterMock->expects($this->once()) - ->method('filter') - ->with(['stock_data']) + $this->stockFilterMock->expects($this->once())->method('filter')->with(['stock_data']) ->willReturn(['stock_data']); - $this->productMock->expects($this->once()) - ->method('isLockedAttribute') - ->with('media') - ->willReturn(true); - $this->productMock->expects($this->once()) - ->method('unlockAttribute') - ->with('media'); - $this->productMock->expects($this->any()) - ->method('getProductLinks') - ->willReturn([]); - $this->productMock->expects($this->once()) - ->method('lockAttribute') - ->with('media'); - $this->productMock->expects($this->once()) - ->method('getAttributes') - ->willReturn($attributesArray); - - $productData['category_ids'] = []; - $productData['website_ids'] = []; - unset($productData['options']); - - $this->productMock->expects($this->once()) - ->method('addData') - ->with($productData); - $this->productMock->expects($this->once()) - ->method('getSku') - ->willReturn('sku'); - $this->productMock->expects($this->any()) - ->method('getOptionsReadOnly') - ->willReturn(false); - + $this->productMock->expects($this->once())->method('isLockedAttribute')->with('media')->willReturn(true); + $this->productMock->expects($this->once())->method('unlockAttribute')->with('media'); + $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); + $this->productMock->expects($this->once())->method('lockAttribute')->with('media'); + $this->productMock->expects($this->once())->method('getAttributes') + ->willReturn([$attributeNonDate, $attributeDate]); + $this->productMock->expects($this->any())->method('getSku')->willReturn('sku'); + $this->productMock->expects($this->any())->method('getOptionsReadOnly')->willReturn(false); + + $customOptionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $firstExpectedCustomOption = clone $customOptionMock; + $firstExpectedCustomOption->setData($optionsData['option2']); + $secondExpectedCustomOption = clone $customOptionMock; + $secondExpectedCustomOption->setData($optionsData['option3']); $this->customOptionFactoryMock->expects($this->any()) ->method('create') - ->with(['data' => $optionsData['option2']]) - ->willReturn($this->customOptionMock); - $this->productMock->expects($this->once()) - ->method('setOptions') - ->with([$this->customOptionMock]); + ->willReturnMap([ + [ + ['data' => $optionsData['option2']], + $firstExpectedCustomOption + ], [ + ['data' => $optionsData['option3']], + $secondExpectedCustomOption + ] + ]); + $website = $this->getMockBuilder(WebsiteInterface::class)->getMockForAbstractClass(); + $website->expects($this->any())->method('getId')->willReturn(1); + $this->storeManagerMock->expects($this->once())->method('isSingleStoreMode')->willReturn($isSingleStore); + $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($website); $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); + $this->assertEquals($expWebsiteIds, $this->productMock->getDataByKey('website_ids')); + + $productOptions = $this->productMock->getOptions(); + $this->assertTrue(2 == count($productOptions)); + list($option2, $option3) = $productOptions; + $this->assertTrue($option2->getOptionId() == $optionsData['option2']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); + $this->assertTrue($option3->getOptionId() == $optionsData['option3']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); + } + + /** + * @return array + */ + public function initializeDataProvider() + { + return [ + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1] + ], + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 0], + 'expected_website_ids' => ['1' => 1] + ], + [ + 'single_store' => false, + 'website_ids' => ['1' => 0, '2' => 0], + 'expected_website_ids' => [] + ], + [ + 'single_store' => true, + 'website_ids' => [], + 'expected_website_ids' => ['1' => 1] + ], + ]; } /** @@ -362,9 +310,9 @@ public function mergeProductOptionsDataProvider() [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val2', + 'title' => 'val2', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', 'values' => [ [ 'option_type_id' => '2', @@ -379,7 +327,7 @@ public function mergeProductOptionsDataProvider() [ 5 => [ 'key1' => '0', - 'key2' => '1', + 'title' => '1', 'values' => [2 => ['key1' => 1]] ] ], @@ -387,9 +335,10 @@ public function mergeProductOptionsDataProvider() [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val4', + 'title' => 'val4', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', + 'is_delete_store_title' => 1, 'values' => [ [ 'option_type_id' => '2', @@ -413,8 +362,9 @@ public function mergeProductOptionsDataProvider() [ 'option_type_id' => '2', 'key1' => 'val1', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'title' => 'val2', + 'default_key1' => 'val11', + 'default_title' => 'val22' ] ] ] @@ -423,7 +373,7 @@ public function mergeProductOptionsDataProvider() 7 => [ 'key1' => '1', 'key2' => '1', - 'values' => [2 => ['key1' => 1, 'key2' => 1]] + 'values' => [2 => ['key1' => 0, 'title' => 1]] ] ], [ @@ -435,9 +385,11 @@ public function mergeProductOptionsDataProvider() 'values' => [ [ 'option_type_id' => '2', - 'key1' => 'val11', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'key1' => 'val1', + 'title' => 'val22', + 'default_key1' => 'val11', + 'default_title' => 'val22', + 'is_delete_store_title' => 1 ] ] ] diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php index a226ce91b24d1..384db2dcb72b6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php @@ -1,6 +1,6 @@ priceProcessor = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price\Processor::class) + $this->priceProcessorMock = $this->getMockBuilder(Processor::class) ->disableOriginalConstructor()->getMock(); + $this->productBuilderMock = $this->getMockBuilder(Builder::class) + ->setMethods(['build']) + ->disableOriginalConstructor() + ->getMock(); - $productBuilder = $this->getMockBuilder( - \Magento\Catalog\Controller\Adminhtml\Product\Builder::class - )->setMethods(['build'])->disableOriginalConstructor()->getMock(); - - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor() - ->setMethods(['getTypeId', 'getStoreId', '__sleep', '__wakeup'])->getMock(); - $product->expects($this->any())->method('getTypeId')->will($this->returnValue('simple')); - $product->expects($this->any())->method('getStoreId')->will($this->returnValue('1')); - $productBuilder->expects($this->any())->method('build')->will($this->returnValue($product)); + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getTypeId', 'getStoreId', '__sleep', '__wakeup']) + ->getMock(); + $productMock->expects($this->any()) + ->method('getTypeId') + ->willReturn('simple'); + $productMock->expects($this->any()) + ->method('getStoreId') + ->willReturn('1'); + $this->productBuilderMock->expects($this->any()) + ->method('build') + ->willReturn($productMock); - $this->resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) + $this->resultRedirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) ->disableOriginalConstructor() ->getMock(); $resultFactory = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) @@ -41,47 +83,71 @@ protected function setUp() $resultFactory->expects($this->atLeastOnce()) ->method('create') ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($this->resultRedirect); + ->willReturn($this->resultRedirectMock); - $abstractDbMock = $this->getMockBuilder(\Magento\Framework\Data\Collection\AbstractDb::class) + $this->abstractDbMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['getAllIds', 'getResource']) ->getMock(); - $abstractDbMock->expects($this->any()) - ->method('getAllIds') - ->willReturn([]); - - $filterMock = $this->getMockBuilder(\Magento\Ui\Component\MassAction\Filter::class) + $this->filterMock = $this->getMockBuilder(\Magento\Ui\Component\MassAction\Filter::class) ->disableOriginalConstructor() ->setMethods(['getCollection']) ->getMock(); - $filterMock->expects($this->any()) - ->method('getCollection') - ->willReturn($abstractDbMock); - - $collectionFactoryMock = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class - ) + $this->actionMock = $this->getMockBuilder(Action::class) + ->disableOriginalConstructor() + ->getMock(); + + $collectionFactoryMock = + $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $collectionFactoryMock->expects($this->any()) ->method('create') - ->willReturn($abstractDbMock); + ->willReturn($this->abstractDbMock); + + $additionalParams = [ + 'resultFactory' => $resultFactory + ]; + /** @var \Magento\Backend\App\Action\Context $context */ + $context = $this->initContext($additionalParams, [[Action::class, $this->actionMock]]); - $additionalParams = ['resultFactory' => $resultFactory]; $this->action = new \Magento\Catalog\Controller\Adminhtml\Product\MassStatus( - $this->initContext($additionalParams), - $productBuilder, - $this->priceProcessor, - $filterMock, + $context, + $this->productBuilderMock, + $this->priceProcessorMock, + $this->filterMock, $collectionFactoryMock ); } public function testMassStatusAction() { - $this->priceProcessor->expects($this->once())->method('reindexList'); + $storeId = 1; + $status = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED; + $filters = [ + 'store_id' => 2, + ]; + + $this->filterMock->expects($this->once()) + ->method('getCollection') + ->willReturn($this->abstractDbMock); + $this->abstractDbMock->expects($this->once()) + ->method('getAllIds') + ->willReturn([3]); + $this->request->expects($this->exactly(3)) + ->method('getParam') + ->willReturnMap([ + ['store', 0, $storeId], + ['status', null, $status], + ['filters', [], $filters] + ]); + $this->actionMock->expects($this->once()) + ->method('updateAttributes') + ->with([3], ['status' => $status], 2); + $this->priceProcessorMock->expects($this->once()) + ->method('reindexList'); + $this->action->execute(); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php index f8754ce00e50d..71f729ffec9d1 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php @@ -1,7 +1,7 @@ getMock(\Magento\Catalog\Model\Product\Action::class, [], [], '', false); - $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('get')->will($this->returnValue($productActionMock)); + + $this->objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); + + if ($objectManagerMap) { + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap($objectManagerMap); + } + + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->willReturn($productActionMock); $block = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) ->disableOriginalConstructor()->getMockForAbstractClass(); @@ -93,7 +111,7 @@ protected function initContext(array $additionalParams = []) $this->context->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager)); $this->context->expects($this->any())->method('getRequest')->will($this->returnValue($requestInterfaceMock)); $this->context->expects($this->any())->method('getResponse')->will($this->returnValue($responseInterfaceMock)); - $this->context->expects($this->any())->method('getObjectManager')->will($this->returnValue($objectManagerMock)); + $this->context->expects($this->any())->method('getObjectManager')->willReturn($this->objectManagerMock); $this->context->expects($this->any())->method('getMessageManager') ->will($this->returnValue($managerInterfaceMock)); diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Category/MoveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Category/MoveTest.php new file mode 100644 index 0000000000000..035218ee57918 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Category/MoveTest.php @@ -0,0 +1,320 @@ +resultJsonFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->layoutFactoryMock = $this->getMockBuilder(\Magento\Framework\View\LayoutFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->context = $this->getMock(\Magento\Backend\App\Action\Context::class, [], [], '', false); + $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->fillContext(); + + $this->moveController = new Move( + $this->context, + $this->resultJsonFactoryMock, + $this->layoutFactoryMock, + $this->loggerMock + ); + $this->initObjectManager(); + } + + private function fillContext() + { + $this->request = $this + ->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->setMethods(['getPost']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->context->expects($this->once())->method('getRequest')->will($this->returnValue($this->request)); + $this->messageManager = $this->getMock(ManagerInterface::class); + $this->context->expects($this->once())->method('getMessageManager')->willReturn($this->messageManager); + } + + private function initObjectManager() + { + $this->objectManager = $this->getMock(ObjectManagerInterface::class); + $moveController = new \ReflectionClass($this->moveController); + $objectManagerProp = $moveController->getProperty('_objectManager'); + $objectManagerProp->setAccessible(true); + $objectManagerProp->setValue($this->moveController, $this->objectManager); + } + + public function testExecuteWithGenericException() + { + $messagesCollection = $this->getMockBuilder(\Magento\Framework\Message\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock = $this->getMockBuilder(\Magento\Framework\View\Element\Messages::class) + ->disableOriginalConstructor() + ->getMock(); + $layoutMock = $this->getMock(\Magento\Framework\View\LayoutInterface::class); + $this->layoutFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($layoutMock); + $layoutMock->expects($this->once()) + ->method('getMessagesBlock') + ->willReturn($messageBlock); + $wysiwigConfig = $this->getMockBuilder(\Magento\Cms\Model\Wysiwyg\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $registry = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $this->request->expects($this->exactly(2)) + ->method('getPost') + ->withConsecutive(['pid', false], ['aid', false]) + ->willReturnMap([['pid', false, 2], ['aid', false, 1]]); + $this->objectManager->expects($this->once()) + ->method('create') + ->with(\Magento\Catalog\Model\Category::class) + ->willReturn($categoryMock); + $this->objectManager->expects($this->any()) + ->method('get') + ->withConsecutive([Registry::class], [Registry::class], [\Magento\Cms\Model\Wysiwyg\Config::class]) + ->willReturnMap([[Registry::class, $registry], [\Magento\Cms\Model\Wysiwyg\Config::class, $wysiwigConfig]]); + $categoryMock->expects($this->once()) + ->method('move') + ->willThrowException(new \Exception( + __('Some exception') + )); + $this->messageManager->expects($this->once()) + ->method('addError') + ->with(__('There was a category move error.')); + $this->messageManager->expects($this->once()) + ->method('getMessages') + ->with(true) + ->willReturn($messagesCollection); + $messageBlock->expects($this->once()) + ->method('setMessages') + ->with($messagesCollection); + $resultJsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock->expects($this->once()) + ->method('getGroupedHtml') + ->willReturn(''); + $resultJsonMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'messages' => '', + 'error' => true + ] + ) + ->willReturn(true); + $this->resultJsonFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($resultJsonMock); + $this->assertTrue($this->moveController->execute()); + } + + public function testExecuteWithLocaliedException() + { + $exceptionMessage = 'Sorry, but we can\'t find the new category you selected.'; + $messagesCollection = $this->getMockBuilder(\Magento\Framework\Message\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock = $this->getMockBuilder(\Magento\Framework\View\Element\Messages::class) + ->disableOriginalConstructor() + ->getMock(); + $layoutMock = $this->getMock(\Magento\Framework\View\LayoutInterface::class); + $this->layoutFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($layoutMock); + $layoutMock->expects($this->once()) + ->method('getMessagesBlock') + ->willReturn($messageBlock); + $wysiwigConfig = $this->getMockBuilder(\Magento\Cms\Model\Wysiwyg\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $registry = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $this->request->expects($this->exactly(2)) + ->method('getPost') + ->withConsecutive(['pid', false], ['aid', false]) + ->willReturnMap([['pid', false, 2], ['aid', false, 1]]); + $this->objectManager->expects($this->once()) + ->method('create') + ->with(\Magento\Catalog\Model\Category::class) + ->willReturn($categoryMock); + $this->objectManager->expects($this->any()) + ->method('get') + ->withConsecutive([Registry::class], [Registry::class], [\Magento\Cms\Model\Wysiwyg\Config::class]) + ->willReturnMap([[Registry::class, $registry], [\Magento\Cms\Model\Wysiwyg\Config::class, $wysiwigConfig]]); + $this->messageManager->expects($this->once()) + ->method('addError') + ->with($exceptionMessage); + $this->messageManager->expects($this->once()) + ->method('getMessages') + ->with(true) + ->willReturn($messagesCollection); + $messageBlock->expects($this->once()) + ->method('setMessages') + ->with($messagesCollection); + $resultJsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock->expects($this->once()) + ->method('getGroupedHtml') + ->willReturn(''); + $resultJsonMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'messages' => '', + 'error' => true + ] + ) + ->willReturn(true); + $categoryMock->expects($this->once()) + ->method('move') + ->willThrowException(new \Magento\Framework\Exception\LocalizedException( + __($exceptionMessage) + )); + $this->resultJsonFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($resultJsonMock); + $this->assertTrue($this->moveController->execute()); + } + + public function testSuccessfullCategorySave() + { + $messagesCollection = $this->getMockBuilder(\Magento\Framework\Message\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock = $this->getMockBuilder(\Magento\Framework\View\Element\Messages::class) + ->disableOriginalConstructor() + ->getMock(); + $layoutMock = $this->getMock(\Magento\Framework\View\LayoutInterface::class); + $this->layoutFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($layoutMock); + $layoutMock->expects($this->once()) + ->method('getMessagesBlock') + ->willReturn($messageBlock); + $wysiwigConfig = $this->getMockBuilder(\Magento\Cms\Model\Wysiwyg\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $registry = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $this->request->expects($this->exactly(2)) + ->method('getPost') + ->withConsecutive(['pid', false], ['aid', false]) + ->willReturnMap([['pid', false, 2], ['aid', false, 1]]); + $this->objectManager->expects($this->once()) + ->method('create') + ->with(\Magento\Catalog\Model\Category::class) + ->willReturn($categoryMock); + $this->objectManager->expects($this->any()) + ->method('get') + ->withConsecutive([Registry::class], [Registry::class], [\Magento\Cms\Model\Wysiwyg\Config::class]) + ->willReturnMap([[Registry::class, $registry], [\Magento\Cms\Model\Wysiwyg\Config::class, $wysiwigConfig]]); + $this->messageManager->expects($this->once()) + ->method('getMessages') + ->with(true) + ->willReturn($messagesCollection); + $messageBlock->expects($this->once()) + ->method('setMessages') + ->with($messagesCollection); + $resultJsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) + ->disableOriginalConstructor() + ->getMock(); + $messageBlock->expects($this->once()) + ->method('getGroupedHtml') + ->willReturn(''); + $resultJsonMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'messages' => '', + 'error' => false + ] + ) + ->willReturn(true); + $this->messageManager->expects($this->once()) + ->method('addSuccess') + ->with(__('You moved the category.')); + $categoryMock->expects($this->once()) + ->method('move') + ->with(2, 1); + $this->resultJsonFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($resultJsonMock); + $this->assertTrue($this->moveController->execute()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php index d8c18300a31f2..de475da82d206 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php @@ -1,6 +1,6 @@ image->expects($this->any()) ->method('isBaseFilePlaceholder') ->willReturn($isBaseFilePlaceholder); - $this->image->expects($this->any()) - ->method('getNewFile') - ->willReturn($newFile); $this->prepareAttributes([], $imageId); @@ -502,7 +497,6 @@ public function getResizedImageInfoDataProvider() 'image_id' => 'test_image_id', 'image_file' => '/path/to/test_image_id.png', 'base_file' => '/path/to/base_image.png', - 'new_file' => '/path/to/base_image.png', 'destination' => 'small_image', 'set_image_file' => true, 'is_cached' => false, @@ -516,7 +510,6 @@ public function getResizedImageInfoDataProvider() 'image_id' => 'test_image_id', 'image_file' => '/path/to/test_image_id.png', 'base_file' => null, - 'new_file' => true, 'destination' => 'small_image', 'set_image_file' => false, 'is_cached' => false, @@ -530,7 +523,6 @@ public function getResizedImageInfoDataProvider() 'image_id' => 'test_image_id', 'image_file' => '/path/to/test_image_id.png', 'base_file' => null, - 'new_file' => false, 'destination' => 'small_image', 'set_image_file' => true, 'is_cached' => false, @@ -544,7 +536,6 @@ public function getResizedImageInfoDataProvider() 'image_id' => 'test_image_id', 'image_file' => '/path/to/test_image_id.png', 'base_file' => null, - 'new_file' => true, 'destination' => 'small_image', 'set_image_file' => true, 'is_cached' => false, @@ -558,7 +549,6 @@ public function getResizedImageInfoDataProvider() 'image_id' => 'test_image_id', 'image_file' => '/path/to/test_image_id.png', 'base_file' => null, - 'new_file' => '/path/to/test_image_id.png', 'destination' => 'small_image', 'set_image_file' => true, 'is_cached' => false, diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php index 29786ecdce969..997db3a409b7f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php @@ -1,6 +1,6 @@ strtr(base64_encode($compareListUrl), '+/=', '-_,'), - 'product' => $productId + Action::PARAM_NAME_URL_ENCODED => '', + 'product' => $productId, + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?'), ]; //Verification - $this->urlBuilder->expects($this->at(0)) - ->method('getUrl') - ->with($compareListUrl) - ->will($this->returnValue($compareListUrl)); - $this->urlBuilder->expects($this->at(1)) + $this->urlBuilder->expects($this->once()) ->method('getUrl') ->with($removeUrl) ->will($this->returnValue($removeUrl)); @@ -159,18 +156,14 @@ public function testGetClearListUrl() public function testGetPostDataClearList() { //Data - $refererUrl = 'home/'; $clearUrl = 'catalog/product_compare/clear'; $postParams = [ - Action::PARAM_NAME_URL_ENCODED => strtr(base64_encode($refererUrl), '+/=', '-_,') + Action::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; //Verification - $this->request->expects($this->once()) - ->method('getServer') - ->with('HTTP_REFERER') - ->will($this->returnValue($refererUrl)); - $this->urlBuilder->expects($this->once()) ->method('getUrl') ->with($clearUrl) diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/ConfigurationPoolTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/ConfigurationPoolTest.php index f7ef5095529ec..a852d5ab3e864 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/Product/ConfigurationPoolTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/ConfigurationPoolTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Framework\App\Helper\Context::class, [], [], '', false); + $optionFactoryMock = $this->getMock(\Magento\Catalog\Model\Product\OptionFactory::class, [], [], '', false); + $filterManagerMock = $this->getMock(\Magento\Framework\Filter\FilterManager::class, [], [], '', false); + $stringUtilsMock = $this->getMock(\Magento\Framework\Stdlib\StringUtils::class, [], [], '', false); + $this->serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); + + $this->helper = new \Magento\Catalog\Helper\Product\Configuration( + $contextMock, + $optionFactoryMock, + $filterManagerMock, + $stringUtilsMock, + $this->serializer + ); + } + + /** + * Retrieves product additional options + */ + public function testGetAdditionalOptionOnly() + { + $additionalOptionResult = ['additional_option' => 1]; + + $itemMock = $this->getMock( + \Magento\Catalog\Model\Product\Configuration\Item\ItemInterface::class, + [], + [], + '', + false + ); + $optionMock = $this->getMock( + \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class, + [], + [], + '', + false + ); + $additionalOptionMock = $this->getMock( + \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class, + [], + [], + '', + false + ); + $productMock = $this->getMock(\Magento\Catalog\Model\Product::class, [], [], '', false); + + $this->serializer->expects($this->once())->method('unserialize')->willReturn($additionalOptionResult); + $optionMock->expects($this->once())->method('getValue')->willReturn(null); + $additionalOptionMock->expects($this->once())->method('getValue'); + + $itemMock->expects($this->once())->method('getProduct')->willReturn($productMock); + $itemMock->expects($this->any())->method('getOptionByCode')->will($this->returnValueMap( + [ + ['option_ids', $optionMock], + ['additional_options', $additionalOptionMock] + ] + )); + + $this->assertEquals($additionalOptionResult, $this->helper->getCustomOptions($itemMock)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/Edit/Action/AttributeTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/Edit/Action/AttributeTest.php index a6b11d7993950..04faa6bdf03d8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/Product/Edit/Action/AttributeTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/Edit/Action/AttributeTest.php @@ -1,6 +1,6 @@ ['test_attribute'], 'group_two' => ['attribute_one', 'attribute_two']]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_merged.xml b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_merged.xml index 131fe397f2e6b..813e9d64af710 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_merged.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_merged.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_one.xml b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_one.xml index 3e11d226e6af4..3fe4cc449c51d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_one.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_one.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_two.xml b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_two.xml index 772a85eafe95e..718895e7117fb 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_two.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Config/_files/attributes_config_two.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/ConfigTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/ConfigTest.php index 358a5ed67e63d..64a26b7cd8664 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/ConfigTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/ConfigTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->attribute = $this->getMockForAbstractClass( + \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, + [], + 'TestAttribute', + false, + false, + true, + ['getName'] + ); + + $this->attribute->expects($this->once()) + ->method('getName') + ->will($this->returnValue('test_attribute')); + + $this->logger = $this->getMockForAbstractClass( + \Psr\Log\LoggerInterface::class, + [], + 'TestLogger', + false, + false, + true, + ['critical'] + ); + + $this->imageUploader = $this->getMock( + \Magento\Catalog\Model\ImageUploader::class, + ['moveFileFromTmp'], + [], + '', + false + ); + } + + /** + * @return array + */ + public function deletedValueDataProvider() + { + return [ + [false], + [['delete' => true]] + ]; + } + + /** + * @dataProvider deletedValueDataProvider + * + * @param array $value + */ + public function testBeforeSaveValueDeletion($value) + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => $value + ]); + + $model->beforeSave($object); + + $this->assertEquals('', $object->getTestAttribute()); + } + + /** + * @return array + */ + public function invalidValueDataProvider() + { + $closure = function () { + return false; + }; + + return [ + [1234], + [true], + [new \stdClass()], + [$closure], + [['a' => 1, 'b' => 2]] + ]; + } + + /** + * @dataProvider invalidValueDataProvider + * + * @param array $value + */ + public function testBeforeSaveValueInvalid($value) + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => $value + ]); + + $model->beforeSave($object); + + $this->assertEquals('', $object->getTestAttribute()); + } + + public function testBeforeSaveAttributeFileName() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => [ + ['name' => 'test123.jpg'] + ] + ]); + + $model->beforeSave($object); + + $this->assertEquals('test123.jpg', $object->getTestAttribute()); + } + + public function testBeforeSaveTemporaryAttribute() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => [ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] + ] + ]); + + $model->beforeSave($object); + + $this->assertEquals([ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] + ], $object->getData('_additional_data_test_attribute')); + } + + public function testBeforeSaveAttributeStringValue() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => 'test123.jpg' + ]); + + $model->beforeSave($object); + + $this->assertEquals('test123.jpg', $object->getTestAttribute()); + $this->assertNull($object->getData('_additional_data_test_attribute')); + } + + /** + * @return \Magento\Catalog\Model\Category\Attribute\Backend\Image + */ + private function setUpModelForAfterSave() + { + $objectManagerMock = $this->getMock( + \Magento\Framework\App\ObjectManager::class, + ['get'], + [], + '', + false + ); + + $imageUploaderMock = $this->imageUploader; + + $objectManagerMock->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($class, $params = []) use ($imageUploaderMock) { + if ($class == \Magento\Catalog\CategoryImageUpload::class) { + return $imageUploaderMock; + } + + return $this->objectManager->get($class, $params); + })); + + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [ + 'objectManager' => $objectManagerMock, + 'logger' => $this->logger + ]); + $this->objectManager->setBackwardCompatibleProperty($model, 'imageUploader', $this->imageUploader); + + return $model->setAttribute($this->attribute); + } + + public function attributeValueDataProvider() + { + return [ + [[['name' => 'test1234.jpg']]], + ['test1234.jpg'], + [''], + [false] + ]; + } + + /** + * @dataProvider attributeValueDataProvider + * + * @param array $value + */ + public function testAfterSaveWithAdditionalData($value) + { + $model = $this->setUpModelForAfterSave(); + + $this->imageUploader->expects($this->once()) + ->method('moveFileFromTmp') + ->with($this->equalTo('test1234.jpg')); + + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => $value, + '_additional_data_test_attribute' => [['name' => 'test1234.jpg']] + ] + ); + + $model->afterSave($object); + } + + /** + * @dataProvider attributeValueDataProvider + * + * @param array $value + */ + public function testAfterSaveWithoutAdditionalData($value) + { + $model = $this->setUpModelForAfterSave(); + + $this->imageUploader->expects($this->never()) + ->method('moveFileFromTmp'); + + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => $value + ] + ); + + $model->afterSave($object); + } + + public function testAfterSaveWithExceptions() + { + $model = $this->setUpModelForAfterSave(); + + $exception = new \Exception(); + + $this->imageUploader->expects($this->any()) + ->method('moveFileFromTmp') + ->will($this->throwException($exception)); + + $this->logger->expects($this->once()) + ->method('critical') + ->with($this->equalTo($exception)); + + $object = new \Magento\Framework\DataObject( + [ + '_additional_data_test_attribute' => [['name' => 'test1234.jpg']] + ] + ); + + $model->afterSave($object); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/SortbyTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/SortbyTest.php index b720856d8554d..62c0fc6d9f535 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/SortbyTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/SortbyTest.php @@ -1,6 +1,6 @@ eavValidationRules = $this->getMockBuilder(EavValidationRules::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->collection = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collection->expects($this->any()) + ->method('addAttributeToSelect') + ->with('*') + ->willReturnSelf(); + + $this->categoryCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->categoryCollectionFactory->expects($this->any()) + ->method('create') + ->willReturn($this->collection); + + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + + $this->registry = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->eavEntityMock = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->eavConfig = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->request = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->categoryFactory = $this->getMockBuilder(CategoryFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfo = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return DataProvider + */ + private function getModel() + { + $this->eavEntityMock->expects($this->any()) + ->method('getAttributeCollection') + ->willReturn([]); + + $this->eavConfig->expects($this->any()) + ->method('getEntityType') + ->with('catalog_category') + ->willReturn($this->eavEntityMock); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $model = $objectManager->getObject( + DataProvider::class, + [ + 'eavValidationRules' => $this->eavValidationRules, + 'categoryCollectionFactory' => $this->categoryCollectionFactory, + 'storeManager' => $this->storeManager, + 'registry' => $this->registry, + 'eavConfig' => $this->eavConfig, + 'request' => $this->request, + 'categoryFactory' => $this->categoryFactory, + ] + ); + + $objectManager->setBackwardCompatibleProperty( + $model, + 'fileInfo', + $this->fileInfo + ); + + return $model; + } + + public function testGetDataNoCategory() + { + $this->registry->expects($this->once()) + ->method('registry') + ->with('category') + ->willReturn(null); + + $model = $this->getModel(); + $this->assertNull($model->getData()); + } + + public function testGetDataNoFileExists() + { + $fileName = 'filename.ext1'; + $categoryId = 1; + + $categoryData = [ + 'image' => $fileName, + ]; + + $imageBackendMock = $this->getMockBuilder(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class) + ->disableOriginalConstructor() + ->getMock(); + + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeMock->expects($this->once()) + ->method('getBackend') + ->willReturn($imageBackendMock); + + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $categoryMock->expects($this->exactly(2)) + ->method('getData') + ->willReturnMap([ + ['', null, $categoryData], + ['image', null, $categoryData['image']], + ]); + $categoryMock->expects($this->any()) + ->method('getExistsStoreValueFlag') + ->with('url_key') + ->willReturn(false); + $categoryMock->expects($this->any()) + ->method('getStoreId') + ->willReturn(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + $categoryMock->expects($this->once()) + ->method('getId') + ->willReturn($categoryId); + $categoryMock->expects($this->once()) + ->method('getAttributes') + ->willReturn(['image' => $attributeMock]); + + $this->registry->expects($this->once()) + ->method('registry') + ->with('category') + ->willReturn($categoryMock); + + $this->fileInfo->expects($this->once()) + ->method('isExist') + ->with($fileName) + ->willReturn(false); + + $model = $this->getModel(); + $result = $model->getData(); + + $this->assertTrue(is_array($result)); + $this->assertArrayHasKey($categoryId, $result); + $this->assertArrayNotHasKey('image', $result[$categoryId]); + } + + public function testGetData() + { + $fileName = 'filename.png'; + $mime = 'image/png'; + $size = 1; + + $categoryId = 1; + $categoryUrl = 'category_url'; + + $categoryData = [ + 'image' => $fileName, + ]; + + $expects = [ + [ + 'name' => $fileName, + 'url' => $categoryUrl, + 'size' => $size, + 'type' => $mime, + ], + ]; + + $imageBackendMock = $this->getMockBuilder(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class) + ->disableOriginalConstructor() + ->getMock(); + + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeMock->expects($this->once()) + ->method('getBackend') + ->willReturn($imageBackendMock); + + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $categoryMock->expects($this->exactly(2)) + ->method('getData') + ->willReturnMap([ + ['', null, $categoryData], + ['image', null, $categoryData['image']], + ]); + $categoryMock->expects($this->any()) + ->method('getExistsStoreValueFlag') + ->with('url_key') + ->willReturn(false); + $categoryMock->expects($this->any()) + ->method('getStoreId') + ->willReturn(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + $categoryMock->expects($this->once()) + ->method('getId') + ->willReturn($categoryId); + $categoryMock->expects($this->once()) + ->method('getAttributes') + ->willReturn(['image' => $attributeMock]); + $categoryMock->expects($this->once()) + ->method('getImageUrl') + ->willReturn($categoryUrl); + + $this->registry->expects($this->once()) + ->method('registry') + ->with('category') + ->willReturn($categoryMock); + + $this->fileInfo->expects($this->once()) + ->method('isExist') + ->with($fileName) + ->willReturn(true); + $this->fileInfo->expects($this->once()) + ->method('getStat') + ->with($fileName) + ->willReturn(['size' => $size]); + $this->fileInfo->expects($this->once()) + ->method('getMimeType') + ->with($fileName) + ->willReturn($mime); + + $model = $this->getModel(); + $result = $model->getData(); + + $this->assertTrue(is_array($result)); + $this->assertArrayHasKey($categoryId, $result); + $this->assertArrayHasKey('image', $result[$categoryId]); + + $this->assertEquals($expects, $result[$categoryId]['image']); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php new file mode 100644 index 0000000000000..973fa8555264a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php @@ -0,0 +1,114 @@ +mediaDirectory = $this->getMockBuilder(WriteInterface::class) + ->getMockForAbstractClass(); + + $this->filesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem->expects($this->any()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($this->mediaDirectory); + + $this->mime = $this->getMockBuilder(Mime::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new FileInfo( + $this->filesystem, + $this->mime + ); + } + + public function testGetMimeType() + { + $mediaPath = '/catalog/category'; + + $fileName = '/filename.ext1'; + $absoluteFilePath = '/absolute_path/catalog/category/filename.ext1'; + + $expected = 'ext1'; + + $this->mediaDirectory->expects($this->once()) + ->method('getAbsolutePath') + ->with($mediaPath. '/' . ltrim($fileName, '/')) + ->willReturn($absoluteFilePath); + + $this->mime->expects($this->once()) + ->method('getMimeType') + ->with($absoluteFilePath) + ->willReturn($expected); + + $this->assertEquals($expected, $this->model->getMimeType($fileName)); + } + + public function testGetStat() + { + $mediaPath = '/catalog/category'; + + $fileName = '/filename.ext1'; + + $expected = ['size' => 1]; + + $this->mediaDirectory->expects($this->once()) + ->method('stat') + ->with($mediaPath . $fileName) + ->willReturn($expected); + + $result = $this->model->getStat($fileName); + + $this->assertTrue(is_array($result)); + $this->assertArrayHasKey('size', $result); + $this->assertEquals(1, $result['size']); + } + + public function testIsExist() + { + $mediaPath = '/catalog/category'; + + $fileName = '/filename.ext1'; + + $this->mediaDirectory->expects($this->once()) + ->method('isExist') + ->with($mediaPath . $fileName) + ->willReturn(true); + + $this->assertTrue($this->model->isExist($fileName)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/ReadHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/ReadHandlerTest.php index 8be5ca3dba1ef..8d1068fb5172e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/ReadHandlerTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Link/ReadHandlerTest.php @@ -1,6 +1,6 @@ context = $this->getMock( - \Magento\Framework\Model\Context::class, - ['getEventDispatcher', 'getCacheManager'], + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->registry = $this->getMock(\Magento\Framework\Registry::class); + $this->storeManager = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->categoryTreeResource = $this->getMock( + \Magento\Catalog\Model\ResourceModel\Category\Tree::class, + [], [], '', false ); - - $this->eventManager = $this->getMock(\Magento\Framework\Event\ManagerInterface::class); - $this->context->expects($this->any())->method('getEventDispatcher') - ->will($this->returnValue($this->eventManager)); - $this->cacheManager = $this->getMock(\Magento\Framework\App\CacheInterface::class); - $this->context->expects($this->any())->method('getCacheManager') - ->will($this->returnValue($this->cacheManager)); - - $this->registry = $this->getMock(\Magento\Framework\Registry::class); - $this->storeManager = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->categoryTreeResource = $this->getMock( - \Magento\Catalog\Model\ResourceModel\Category\Tree::class, - [], - [], - '', - false - ); - $this->categoryTreeFactory = $this->getMock( + $this->categoryTreeFactory = $this->getMock( \Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class, ['create'], [], '', false); $this->categoryRepository = $this->getMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class); - $this->storeCollectionFactory = $this->getMock( + $this->storeCollectionFactory = $this->getMock( \Magento\Store\Model\ResourceModel\Store\CollectionFactory::class, ['create'], [], @@ -130,7 +149,7 @@ protected function setUp() false ); $this->url = $this->getMock(\Magento\Framework\UrlInterface::class); - $this->productCollectionFactory = $this->getMock( + $this->productCollectionFactory = $this->getMock( \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class, ['create'], [], @@ -138,7 +157,7 @@ protected function setUp() false ); $this->catalogConfig = $this->getMock(\Magento\Catalog\Model\Config::class, [], [], '', false); - $this->filterManager = $this->getMock( + $this->filterManager = $this->getMock( \Magento\Framework\Filter\FilterManager::class, ['translitUrl'], [], @@ -148,7 +167,7 @@ protected function setUp() $this->flatState = $this->getMock(\Magento\Catalog\Model\Indexer\Category\Flat\State::class, [], [], '', false); $this->flatIndexer = $this->getMock(\Magento\Framework\Indexer\IndexerInterface::class); $this->productIndexer = $this->getMock(\Magento\Framework\Indexer\IndexerInterface::class); - $this->categoryUrlPathGenerator = $this->getMock( + $this->categoryUrlPathGenerator = $this->getMock( \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class, [], [], @@ -156,19 +175,19 @@ protected function setUp() false ); $this->urlFinder = $this->getMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); - $this->resource = $this->getMock( + $this->resource = $this->getMock( \Magento\Catalog\Model\ResourceModel\Category::class, [], [], '', false ); - $this->indexerRegistry = $this->getMock( - \Magento\Framework\Indexer\IndexerRegistry::class, - ['get'], - [], - '', - false + $this->indexerRegistry = $this->getMock( + \Magento\Framework\Indexer\IndexerRegistry::class, + ['get'], + [], + '', + false ); $this->metadataServiceMock = $this->getMock(\Magento\Catalog\Api\CategoryAttributeRepositoryInterface::class); @@ -198,7 +217,7 @@ public function testFormatUrlKey() public function testMoveWhenCannotFindParentCategory() { $this->markTestIncomplete('MAGETWO-31165'); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -223,7 +242,7 @@ public function testMoveWhenCannotFindParentCategory() */ public function testMoveWhenCannotFindNewCategory() { - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -250,7 +269,7 @@ public function testMoveWhenCannotFindNewCategory() public function testMoveWhenParentCategoryIsSameAsChildCategory() { $this->markTestIncomplete('MAGETWO-31165'); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -277,7 +296,7 @@ public function testMovePrimaryWorkflow() ->method('get') ->with('catalog_category_product') ->will($this->returnValue($indexer)); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -313,10 +332,9 @@ public function testGetUseFlatResourceTrue() protected function getCategoryModel() { - return (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + return $this->objectManager->getObject( \Magento\Catalog\Model\Category::class, [ - 'context' => $this->context, 'registry' => $this->registry, 'storeManager' => $this->storeManager, 'categoryTreeResource' => $this->categoryTreeResource, @@ -487,4 +505,76 @@ public function testGetCustomAttributes() $this->category->getCustomAttribute($descriptionAttributeCode)->getValue() ); } + + /** + * @return array + */ + public function getImageWithAttributeCodeDataProvider() + { + return [ + ['testimage', 'http://www.example.com/catalog/category/testimage'], + [false, false] + ]; + } + + /** + * @param string|bool $value + * @param string|bool $url + * + * @dataProvider getImageWithAttributeCodeDataProvider + */ + public function testGetImageWithAttributeCode($value, $url) + { + $storeManager = $this->getMock(\Magento\Store\Model\StoreManager::class, ['getStore'], [], '', false); + $store = $this->getMock(\Magento\Store\Model\Store::class, ['getBaseUrl'], [], '', false); + + $storeManager->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($store)); + + $store->expects($this->any()) + ->method('getBaseUrl') + ->with(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) + ->will($this->returnValue('http://www.example.com/')); + + /** @var \Magento\Catalog\Model\Category $model */ + $model = $this->objectManager->getObject( + \Magento\Catalog\Model\Category::class, + [ + 'storeManager' => $storeManager + ] + ); + + $model->setData('attribute1', $value); + + $result = $model->getImageUrl('attribute1'); + + $this->assertEquals($url, $result); + } + + public function testGetImageWithoutAttributeCode() + { + $storeManager = $this->getMock(\Magento\Store\Model\StoreManager::class, ['getStore'], [], '', false); + $store = $this->getMock(\Magento\Store\Model\Store::class, ['getBaseUrl'], [], '', false); + + $storeManager->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($store)); + + $store->expects($this->any()) + ->method('getBaseUrl') + ->with(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) + ->will($this->returnValue('http://www.example.com/')); + + /** @var \Magento\Catalog\Model\Category $model */ + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category::class, [ + 'storeManager' => $storeManager + ]); + + $model->setData('image', 'myimage'); + + $result = $model->getImageUrl(); + + $this->assertEquals('http://www.example.com/catalog/category/myimage', $result); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php new file mode 100644 index 0000000000000..b543c92ac0875 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php @@ -0,0 +1,110 @@ +productMock = $this->getMock(Product::class, [], [], '', false, false); + $this->converterPoolMock = $this->getMock(ConverterPool::class, [], [], '', false, false); + $this->providerMock = $this->getMock(CollectionProviderInterface::class); + $this->converterMock = $this->getMock(ConverterInterface::class); + + $this->model = new CollectionProvider($this->converterPoolMock, ['crosssell' => $this->providerMock]); + } + + /** + * Test sort order of linked products based on configured item position. + */ + public function testGetCollection() + { + $linkedProductOneMock = $this->getMock(Product::class, [], [], '', false, false); + $linkedProductTwoMock = $this->getMock(Product::class, [], [], '', false, false); + $linkedProductThreeMock = $this->getMock(Product::class, [], [], '', false, false); + + $linkedProductOneMock->expects($this->once())->method('getId')->willReturn(1); + $linkedProductTwoMock->expects($this->once())->method('getId')->willReturn(2); + $linkedProductThreeMock->expects($this->once())->method('getId')->willReturn(3); + + $this->converterPoolMock->expects($this->once()) + ->method('getConverter') + ->with('crosssell') + ->willReturn($this->converterMock); + + $map = [ + [$linkedProductOneMock, ['name' => 'Product One', 'position' => 10]], + [$linkedProductTwoMock, ['name' => 'Product Two', 'position' => 2]], + [$linkedProductThreeMock, ['name' => 'Product Three', 'position' => 2]], + ]; + + $this->converterMock->expects($this->exactly(3))->method('convert')->willReturnMap($map); + + $this->providerMock->expects($this->once()) + ->method('getLinkedProducts') + ->with($this->productMock) + ->willReturn( + [ + $linkedProductOneMock, + $linkedProductTwoMock, + $linkedProductThreeMock + ] + ); + + $expectedResult = [ + 2 => ['name' => 'Product Two', 'position' => 2], + 3 => ['name' => 'Product Three', 'position' => 2], + 10 => ['name' => 'Product One', 'position' => 10], + ]; + + $actualResult = $this->model->getCollection($this->productMock, 'crosssell'); + + $this->assertEquals($expectedResult, $actualResult, 'Sort order of linked products in incorrect'); + } + + /** + * Test exception when collection provider is not configured for product link type. + * + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + * @expectedExceptionMessage Collection provider is not registered + */ + public function testGetCollectionWithMissingProviders() + { + $this->model->getCollection($this->productMock, 'upsell'); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php index 6a0d695d71f39..fb648ba03253a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php @@ -1,6 +1,6 @@ objectFactory = $this->getMockBuilder(\Magento\Framework\DataObject\Factory::class) @@ -90,12 +93,16 @@ protected function setUp() $this->buyRequest = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->disableOriginalConstructor() ->getMock(); + $this->serializer = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) + ->setMethods(['unserialize']) + ->getMockForAbstractClass(); $this->processor = new CustomOptionProcessor( $this->objectFactory, $this->productOptionFactory, $this->extensionFactory, - $this->customOptionFactory + $this->customOptionFactory, + $this->serializer ); } @@ -126,6 +133,9 @@ public function testConvertToBuyRequest() $this->assertSame($this->buyRequest, $this->processor->convertToBuyRequest($this->cartItem)); } + /** + * @covers \Magento\Catalog\Model\CustomOptions\CustomOptionProcessor::getOptions() + */ public function testProcessCustomOptions() { $optionId = 23; @@ -136,9 +146,12 @@ public function testProcessCustomOptions() ->method('getOptionByCode') ->with('info_buyRequest') ->willReturn($quoteItemOption); - $quoteItemOption->expects($this->once()) + $quoteItemOption->expects($this->any()) ->method('getValue') - ->willReturn('a:1:{s:7:"options";a:1:{i:' . $optionId . ';a:2:{i:0;s:1:"5";i:1;s:1:"6";}}} '); + ->willReturn('{"options":{"' . $optionId . '":["5","6"]}}'); + $this->serializer->expects($this->any()) + ->method('unserialize') + ->willReturn(json_decode($quoteItemOption->getValue(), true)); $this->customOptionFactory->expects($this->once()) ->method('create') ->willReturn($this->customOption); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CustomOptions/CustomOptionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CustomOptions/CustomOptionTest.php index d7a0fdb778d4c..f6fbbab61f8a0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CustomOptions/CustomOptionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CustomOptions/CustomOptionTest.php @@ -1,6 +1,6 @@ flatIndexerMock = $this->getMockBuilder(\Magento\Catalog\Helper\Product\Flat\Indexer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->tableDataMock = $this->getMockBuilder( + \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->metadataMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\EntityMetadataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->metadataMock->expects($this->any())->method('getLinkField')->willReturn('entity_id'); + + $this->flatTableBuilder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder::class, + [ + 'productIndexerHelper' => $this->flatIndexerMock, + 'resource' => $this->resourceMock, + 'config' => $this->scopeConfigMock, + 'storeManager' => $this->storeManagerMock, + 'tableData' => $this->tableDataMock, + '_connection' => $this->connectionMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->flatTableBuilder, + 'metadataPool', + $this->metadataPoolMock + ); + } + + public function testBuild() + { + $storeId = 1; + $changedIds = []; + $valueFieldSuffix = '_value'; + $tableDropSuffix = ''; + $fillTmpTables = true; + $tableName = 'catalog_product_entity'; + $attributeTable = 'catalog_product_entity_int'; + $temporaryTableName = 'catalog_product_entity_int_tmp_indexer'; + $temporaryValueTableName = 'catalog_product_entity_int_tmp_indexer_value'; + $linkField = 'entity_id'; + $statusId = 22; + $eavCustomField = 'space_weight'; + $eavCustomValueField = $eavCustomField . $valueFieldSuffix; + $this->flatIndexerMock->expects($this->once())->method('getAttributes')->willReturn([]); + $this->flatIndexerMock->expects($this->exactly(3))->method('getFlatColumns') + ->willReturnOnConsecutiveCalls([], [$eavCustomValueField => []], [$eavCustomValueField => []]); + $this->flatIndexerMock->expects($this->once())->method('getFlatIndexes')->willReturn([]); + $statusAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $eavCustomAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flatIndexerMock->expects($this->once())->method('getTablesStructure') + ->willReturn( + [ + 'catalog_product_entity' => [$linkField => $statusAttributeMock], + 'catalog_product_entity_int' => [ + $linkField => $statusAttributeMock, + $eavCustomField => $eavCustomAttributeMock + ] + ] + ); + $this->flatIndexerMock->expects($this->atLeastOnce())->method('getTable') + ->withConsecutive([$tableName], ['catalog_product_website']) + ->willReturnOnConsecutiveCalls($tableName, 'catalog_product_website'); + $this->flatIndexerMock->expects($this->once())->method('getAttribute') + ->with('status') + ->willReturn($statusAttributeMock); + $backendMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend::class) + ->disableOriginalConstructor() + ->getMock(); + $backendMock->expects($this->atLeastOnce())->method('getTable')->willReturn($attributeTable); + $statusAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( + $backendMock + ); + $eavCustomAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( + $backendMock + ); + $statusAttributeMock->expects($this->atLeastOnce())->method('getId')->willReturn($statusId); + $tableMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->any())->method('newTable')->willReturn($tableMock); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + $selectMock->expects($this->once())->method('from')->with( + ['et' => 'catalog_product_entity_tmp_indexer'], + [$linkField, 'type_id', 'attribute_set_id'] + )->willReturnSelf(); + $selectMock->expects($this->atLeastOnce())->method('joinInner')->willReturnSelf(); + $selectMock->expects($this->exactly(3))->method('joinLeft') + ->withConsecutive( + [ + ['dstatus' => $attributeTable], + sprintf( + 'e.%s = dstatus.%s AND dstatus.store_id = %s AND dstatus.attribute_id = %s', + $linkField, + $linkField, + $storeId, + $statusId + ), + [] + ], + [ + $temporaryTableName, + "e.{$linkField} = {$temporaryTableName}.{$linkField}", + [$linkField, $eavCustomField] + ], + [ + $temporaryValueTableName, + "e.{$linkField} = {$temporaryValueTableName}.{$linkField}", + [$eavCustomValueField] + ] + )->willReturnSelf(); + $this->metadataPoolMock->expects($this->atLeastOnce())->method('getMetadata')->with(ProductInterface::class) + ->willReturn($this->metadataMock); + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); + $this->flatTableBuilder->build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/IndexerConfigDataTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/IndexerConfigDataTest.php index 53d1a8fd004a0..097d70a5cf7cf 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/IndexerConfigDataTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/IndexerConfigDataTest.php @@ -1,6 +1,6 @@ connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $table->expects($this->once())->method('addColumn') + ->with('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER) + ->willReturnSelf(); + $tableName = 'test_table'; + $this->connectionMock->expects($this->once()) + ->method('newTable') + ->with($tableName) + ->willReturn($table); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + /** + * @var $builder \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder + */ + $builder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder::class, + [ + 'connection' => $this->connectionMock, + 'tableName' => $tableName + ] + ); + $this->assertEquals($builder, $builder->addColumn('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/TableDataTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/TableDataTest.php index 0f47a675c8265..544bd865a5073 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/TableDataTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/TableDataTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $localeFormatMock = $this->getMock(\Magento\Framework\Locale\FormatInterface::class, [], [], '', false); $groupManagement = $this->getMock(\Magento\Customer\Api\GroupManagementInterface::class, [], [], '', false); - $metadataPool = $this->getMock(\Magento\Framework\EntityManager\MetadataPool::class, [], [], '', false); + $scopeOverriddenValue = $this->getMock( + \Magento\Catalog\Model\Attribute\ScopeOverriddenValue::class, + [], + [], + '', + false + ); $this->_model = $this->getMockForAbstractClass( \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice::class, [ @@ -50,7 +56,7 @@ protected function setUp() 'localeFormat' => $localeFormatMock, 'catalogProductType' => $productTypeMock, 'groupManagement' => $groupManagement, - 'metadataPool' => $metadataPool + 'scopeOverriddenValue' => $scopeOverriddenValue ] ); $resource = $this->getMock(\StdClass::class, ['getMainTable']); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php index 07b4b10834c71..827fe62fd6002 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php @@ -1,6 +1,6 @@ getMockForAbstractClass( - \Magento\Framework\App\ScopeResolverInterface::class, - [], - '', - false - ); - $localeResolver = $this->getMockForAbstractClass( - \Magento\Framework\Locale\ResolverInterface::class, - [], - '', - false - ); - $currencyFactory = $this->getMock(\Magento\Directory\Model\CurrencyFactory::class, [], [], '', false); - $localeFormat = $objectHelper->getObject( - \Magento\Framework\Locale\Format::class, - [ - 'scopeResolver' => $scopeResolver, - 'localeResolver' => $localeResolver, - 'currencyFactory' => $currencyFactory, - ] - ); - - // the model we are testing + $localeFormat = $objectHelper->getObject(\Magento\Framework\Locale\Format::class); + $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->getMockForAbstractClass(); + $this->currencyFactory = $this->getMockBuilder(\Magento\Directory\Model\CurrencyFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor()->getMock(); $this->model = $objectHelper->getObject( \Magento\Catalog\Model\Product\Attribute\Backend\Price::class, - ['localeFormat' => $localeFormat] - ); - - $attribute = $this->getMockForAbstractClass( - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, - [], - '', - false + [ + 'localeFormat' => $localeFormat, + 'storeManager' => $this->storeManager, + 'currencyFactory' => $this->currencyFactory + ] ); - $this->model->setAttribute($attribute); + $this->attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->setMethods(['getAttributeCode', 'isScopeWebsite']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->model->setAttribute($this->attribute); } /** @@ -111,4 +111,71 @@ public function dataProviderValidateForFailure() 'negative Lebanon' => ['-1 234'], ]; } + + public function testAfterSaveWithDifferentStores() + { + $newPrice = '9.99'; + $attributeCode = 'price'; + $defaultStoreId = 0; + $allStoreIds = [1, 2, 3]; + $object = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor()->getMock(); + $object->expects($this->any())->method('getData')->with($attributeCode)->willReturn($newPrice); + $object->expects($this->any())->method('getOrigData')->with($attributeCode)->willReturn('7.77'); + $object->expects($this->any())->method('getStoreId')->willReturn($defaultStoreId); + $object->expects($this->never())->method('getStoreIds'); + $object->expects($this->never())->method('getWebsiteStoreIds'); + $this->attribute->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + $this->attribute->expects($this->any())->method('isScopeWebsite') + ->willReturn(\Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_WEBSITE); + $this->storeManager->expects($this->never())->method('getStore'); + + $object->expects($this->any())->method('addAttributeUpdate')->withConsecutive( + [ + $this->equalTo($attributeCode), + $this->equalTo($newPrice), + $this->equalTo($allStoreIds[0]) + ], + [ + $this->equalTo($attributeCode), + $this->equalTo($newPrice), + $this->equalTo($allStoreIds[1]) + ], + [ + $this->equalTo($attributeCode), + $this->equalTo($newPrice), + $this->equalTo($allStoreIds[2]) + ] + ); + $this->assertEquals($this->model, $this->model->afterSave($object)); + } + + public function testAfterSaveWithOldPrice() + { + $attributeCode = 'price'; + + $object = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor()->getMock(); + $object->expects($this->any())->method('getData')->with($attributeCode)->willReturn('7.77'); + $object->expects($this->any())->method('getOrigData')->with($attributeCode)->willReturn('7.77'); + $this->attribute->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + $this->attribute->expects($this->any())->method('getIsGlobal') + ->willReturn(\Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_WEBSITE); + + $object->expects($this->never())->method('addAttributeUpdate'); + $this->assertEquals($this->model, $this->model->afterSave($object)); + } + + public function testAfterSaveWithGlobalPrice() + { + $attributeCode = 'price'; + + $object = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor()->getMock(); + $object->expects($this->any())->method('getData')->with($attributeCode)->willReturn('9.99'); + $object->expects($this->any())->method('getOrigData')->with($attributeCode)->willReturn('7.77'); + $this->attribute->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + $this->attribute->expects($this->any())->method('getIsGlobal') + ->willReturn(\Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL); + + $object->expects($this->never())->method('addAttributeUpdate'); + $this->assertEquals($this->model, $this->model->afterSave($object)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php index ec9cb624db79a..74a42fa2cc845 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php @@ -1,6 +1,6 @@ getStockData(); $this->assertEquals(0, $stockData['qty']); } + + public function testBeforeSaveNoStockData() + { + $object = new \Magento\Framework\DataObject( + [ + self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0] + ] + ); + + $this->model->beforeSave($object); + $this->assertNull($object->getStockData()); + $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/WeightTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/WeightTest.php index 0ca0cdf7818b4..7d1bf213b7e34 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/WeightTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/WeightTest.php @@ -1,6 +1,6 @@ model->save($attributeMock); } + + public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload() + { + $labelMock = $this->getMock(AttributeFrontendLabelInterface::class); + $labelMock->expects($this->any())->method('getStoreId')->willReturn(1); + $labelMock->expects($this->any())->method('getLabel')->willReturn('Store Scope Label'); + + $attributeId = 1; + $attributeCode = 'existing_attribute_code'; + $attributeMock = $this->getMock(Attribute::class, [], [], '', false); + $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + $attributeMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId); + $attributeMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn('Default Label'); + $attributeMock->expects($this->any())->method('getFrontendLabels')->willReturn([$labelMock]); + $attributeMock->expects($this->any())->method('getOptions')->willReturn([]); + + + $existingModelMock = $this->getMock(Attribute::class, [], [], '', false); + $existingModelMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId); + $existingModelMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + + $this->eavAttributeRepositoryMock->expects($this->any()) + ->method('get') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode) + ->willReturn($existingModelMock); + + $attributeMock->expects($this->once()) + ->method('setDefaultFrontendLabel') + ->with( + [ + 0 => 'Default Label', + 1 => 'Store Scope Label' + ] + ); + $this->attributeResourceMock->expects($this->once())->method('save')->with($attributeMock); + + $this->model->save($attributeMock); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/SetManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/SetManagementTest.php index 25553562d2a0b..6d70c1611cd5c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/SetManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/SetManagementTest.php @@ -1,7 +1,7 @@ storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); $this->storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); $this->cacheConfig = $this->getMock(\Magento\Framework\App\Cache\Type\Config::class, [], [], '', false); $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->countryOfManufacture = $this->objectManagerHelper->getObject( + \Magento\Catalog\Model\Product\Attribute\Source\Countryofmanufacture::class, + [ + 'storeManager' => $this->storeManagerMock, + 'configCacheType' => $this->cacheConfig, + ] + ); + + $this->serializerMock = $this->getMock(SerializerInterface::class, [], [], '', false); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->countryOfManufacture, + 'serializer', + $this->serializerMock + ); } /** @@ -51,15 +75,10 @@ public function testGetAllOptions($cachedDataSrl, $cachedDataUnsrl) ->method('load') ->with($this->equalTo('COUNTRYOFMANUFACTURE_SELECT_STORE_store_code')) ->will($this->returnValue($cachedDataSrl)); - - $countryOfManufacture = $this->objectManagerHelper->getObject( - \Magento\Catalog\Model\Product\Attribute\Source\Countryofmanufacture::class, - [ - 'storeManager' => $this->storeManagerMock, - 'configCacheType' => $this->cacheConfig, - ] - ); - $this->assertEquals($cachedDataUnsrl, $countryOfManufacture->getAllOptions()); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn($cachedDataUnsrl); + $this->assertEquals($cachedDataUnsrl, $this->countryOfManufacture->getAllOptions()); } /** @@ -71,7 +90,7 @@ public function testGetAllOptionsDataProvider() { return [ - ['cachedDataSrl' => 'a:1:{s:3:"key";s:4:"data";}', 'cachedDataUnsrl' => ['key' => 'data']] + ['cachedDataSrl' => json_encode(['key' => 'data']), 'cachedDataUnsrl' => ['key' => 'data']] ]; } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Source/InputtypeTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Source/InputtypeTest.php index 8362fc8a76180..2e9ea0f698514 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Source/InputtypeTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Source/InputtypeTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface::class); $entryId = 42; + $entrySecondId = 43; $this->productRepositoryMock->expects($this->once())->method('get')->with($productSku) ->willReturn($this->productMock); $existingEntryMock = $this->getMock( \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface::class ); + $existingSecondEntryMock = $this->getMock( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface::class + ); + $existingEntryMock->expects($this->once())->method('getId')->willReturn($entryId); + $existingEntryMock->expects($this->once())->method('getTypes')->willReturn(['small_image']); + $existingEntryMock->expects($this->once())->method('setTypes')->with(['small_image']); + $existingSecondEntryMock->expects($this->once())->method('getId')->willReturn($entrySecondId); + $existingSecondEntryMock->expects($this->once())->method('getTypes')->willReturn(['image']); + $existingSecondEntryMock->expects($this->once())->method('setTypes')->with([]); $this->productMock->expects($this->once())->method('getMediaGalleryEntries') - ->willReturn([$existingEntryMock]); - $entryMock->expects($this->once())->method('getId')->willReturn($entryId); + ->willReturn([$existingEntryMock, $existingSecondEntryMock]); + + $entryMock->expects($this->exactly(2))->method('getId')->willReturn($entryId); $entryMock->expects($this->once())->method('getFile')->willReturn("base64"); $entryMock->expects($this->once())->method('setId')->with(null); + $entryMock->expects($this->exactly(2))->method('getTypes')->willReturn(['image']); $this->productMock->expects($this->once())->method('setMediaGalleryEntries') - ->willReturn([$entryMock]); + ->with([$entryMock, $existingSecondEntryMock]) + ->willReturnSelf(); $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock); $this->assertTrue($this->model->update($productSku, $entryMock)); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/MimeTypeExtensionMapTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/MimeTypeExtensionMapTest.php index ebf351da74102..8ec55bcec098a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/MimeTypeExtensionMapTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/MimeTypeExtensionMapTest.php @@ -1,7 +1,7 @@ getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + + $productMock->expects($this->exactly($setDataExpectsCalls)) + ->method('setData') + ->with($setDataArgument, 'no_selection'); + + $this->mediaConfig->expects($this->once()) + ->method('getMediaAttributeCodes') + ->willReturn(['image', 'small_image']); + + $this->assertSame($this->model, $this->model->clearMediaAttribute($productMock, $mediaAttribute)); + } + + /** + * @return array + */ + public function clearMediaAttributeDataProvider() + { + return [ + [ + 'setDataExpectsCalls' => 1, + 'setDataArgument' => 'image', + 'mediaAttribute' => 'image', + ], + [ + 'setDataExpectsCalls' => 1, + 'setDataArgument' => 'image', + 'mediaAttribute' => ['image'], + ], + [ + 'setDataExpectsCalls' => 0, + 'setDataArgument' => null, + 'mediaAttribute' => 'some_image', + ], + [ + 'setDataExpectsCalls' => 0, + 'setDataArgument' => null, + 'mediaAttribute' => ['some_image'], + ], + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php index de60264c33163..f12638ac40c8f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php @@ -1,6 +1,6 @@ context = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); @@ -99,7 +119,6 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create', 'isFile', 'isExist', 'getAbsolutePath']) ->getMock(); - $this->mediaDirectory->expects($this->once())->method('create')->will($this->returnValue(true)); $this->filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); $this->filesystem->expects($this->once())->method('getDirectoryWrite') @@ -110,20 +129,49 @@ protected function setUp() $this->fileSystem = $this->getMock(\Magento\Framework\View\FileSystem::class, [], [], '', false); $this->scopeConfigInterface = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $context = $this->getMockBuilder(\Magento\Framework\Model\Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->image = new \Magento\Catalog\Model\Product\Image( + $context, + $this->registry, + $this->storeManager, + $this->config, + $this->coreFileHelper, + $this->filesystem, + $this->factory, + $this->repository, + $this->fileSystem, + $this->scopeConfigInterface + ); + //Settings for backward compatible property $objectManagerHelper = new ObjectManagerHelper($this); - $this->image = $objectManagerHelper->getObject( - \Magento\Catalog\Model\Product\Image::class, - [ - 'registry' => $this->registry, - 'storeManager' => $this->storeManager, - 'catalogProductMediaConfig' => $this->config, - 'coreFileStorageDatabase' => $this->coreFileHelper, - 'filesystem' => $this->filesystem, - 'imageFactory' => $this->factory, - 'assetRepo' => $this->repository, - 'viewFileSystem' => $this->fileSystem, - 'scopeConfig' => $this->scopeConfigInterface - ] + $this->imageAsset = $this->getMockBuilder(\Magento\Framework\View\Asset\LocalInterface::class) + ->getMockForAbstractClass(); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->image, + 'imageAsset', + $this->imageAsset + ); + + $this->viewAssetImageFactory = $this->getMockBuilder(ImageFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->image, + 'viewAssetImageFactory', + $this->viewAssetImageFactory + ); + + $this->viewAssetPlaceholderFactory = $this->getMockBuilder(PlaceholderFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->image, + 'viewAssetPlaceholderFactory', + $this->viewAssetPlaceholderFactory ); } @@ -177,18 +225,39 @@ public function testSetGetBaseFile() $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/somefile.png'; $this->mediaDirectory->expects($this->any())->method('getAbsolutePath') ->will($this->returnValue($absolutePath)); + $this->viewAssetImageFactory->expects($this->any()) + ->method('create') + ->with( + [ + 'miscParams' => [ + 'image_type' => null, + 'image_height' => null, + 'image_width' => null, + 'keep_aspect_ratio' => 'proportional', + 'keep_frame' => 'frame', + 'keep_transparency' => 'transparency', + 'constrain_only' => 'doconstrainonly', + 'background' => 'ffffff', + 'angle' => null, + 'quality' => 80, + ], + 'filePath' => '/somefile.png', + ] + ) + ->willReturn($this->imageAsset); + $this->viewAssetPlaceholderFactory->expects($this->never())->method('create'); + + $this->imageAsset->expects($this->any())->method('getSourceFile')->willReturn('catalog/product/somefile.png'); $this->image->setBaseFile('/somefile.png'); $this->assertEquals('catalog/product/somefile.png', $this->image->getBaseFile()); - $this->assertEquals( - 'catalog/product/cache/1//beff4985b56e3afdbeabfc89641a4582/somefile.png', - $this->image->getNewFile() - ); } public function testSetBaseNoSelectionFile() { - $this->image->setBaseFile('/no_selection'); - $this->assertTrue($this->image->getNewFile()); + $this->viewAssetPlaceholderFactory->expects($this->once())->method('create')->willReturn($this->imageAsset); + $this->imageAsset->expects($this->any())->method('getSourceFile')->willReturn('Default Placeholder Path'); + $this->image->setBaseFile('no_selection'); + $this->assertEquals('Default Placeholder Path', $this->image->getBaseFile()); } public function testSetGetImageProcessor() @@ -284,45 +353,45 @@ public function testSaveFile() )->disableOriginalConstructor()->getMock(); $this->image->setImageProcessor($imageProcessor); $this->coreFileHelper->expects($this->once())->method('saveFile')->will($this->returnValue(true)); - $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/somefile.png'; - $this->mediaDirectory->expects($this->once())->method('getAbsolutePath') - ->will($this->returnValue($absolutePath)); $this->image->saveFile(); } public function testSaveFileNoSelection() { - $this->testSetBaseNoSelectionFile(); + $imageProcessor = $this->getMockBuilder( + \Magento\Framework\Image::class + )->disableOriginalConstructor()->getMock(); + $this->image->setImageProcessor($imageProcessor); $this->assertSame($this->image, $this->image->saveFile()); } public function testGetUrl() { $this->testSetGetBaseFile(); - $url = $this->image->getUrl(); - $this->assertEquals( - 'http://magento.com/media/catalog/product/cache/1//beff4985b56e3afdbeabfc89641a4582/somefile.png', - $url - ); + $this->imageAsset->expects($this->any())->method('getUrl')->will($this->returnValue('url of exist image')); + $this->assertEquals('url of exist image', $this->image->getUrl()); } public function testGetUrlNoSelection() { - $this->testSetBaseNoSelectionFile(); - $this->repository->expects($this->once())->method('getUrl')->will($this->returnValue('someurl')); - $this->assertEquals('someurl', $this->image->getUrl()); + $this->viewAssetPlaceholderFactory->expects($this->once())->method('create')->willReturn($this->imageAsset); + $this->imageAsset->expects($this->any())->method('getUrl')->will($this->returnValue('Default Placeholder URL')); + $this->image->setBaseFile('no_selection'); + $this->assertEquals('Default Placeholder URL', $this->image->getUrl()); } public function testSetGetDestinationSubdir() { - $this->image->setDestinationSubdir('somesubdir'); - $this->assertEquals('somesubdir', $this->image->getDestinationSubdir()); + $this->image->setDestinationSubdir('image_type'); + $this->assertEquals('image_type', $this->image->getDestinationSubdir()); } public function testIsCached() { $this->testSetGetBaseFile(); + $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png'; + $this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath); $this->assertTrue($this->image->isCached()); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Initialization/Helper/ProductLinksTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Initialization/Helper/ProductLinksTest.php index 34cb5c81a1291..681e662c69ccd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Initialization/Helper/ProductLinksTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Initialization/Helper/ProductLinksTest.php @@ -1,6 +1,6 @@ getMock( - \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory::class, - ['create'], - [], - '', - false - ); + $this->optionCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); $metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) ->disableOriginalConstructor() ->getMock(); @@ -96,7 +100,7 @@ protected function setUp() $this->optionRepository, [ 'optionFactory' => $optionFactory, - 'optionCollectionFactory' => $optionCollectionFactory, + 'collectionFactory' => $this->optionCollectionFactory, 'metadataPool' => $metadataPool ] ); @@ -240,4 +244,75 @@ private function setProperties($object, $properties = []) } } } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage ProductSku should be specified + */ + public function testSaveCouldNotSaveException() + { + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn(null); + $this->optionRepository->save($this->optionMock); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSaveNoSuchEntityException() + { + $productSku = 'simple_product'; + $optionId = 1; + $productOptionId = 2; + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $productOption = clone $this->optionMock; + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $productOption->expects($this->any())->method('getOptionId')->willReturn($productOptionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([$productOption]); + $this->optionRepository->save($this->optionMock); + } + + public function testSave() + { + $productSku = 'simple_product'; + $optionId = 1; + $originalValue1 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) + ->disableOriginalConstructor() + ->getMock(); + $originalValue2 = clone $originalValue1; + $originalValue3 = clone $originalValue1; + + $originalValue1->expects($this->at(0))->method('getData')->with('option_type_id')->willReturn(10); + $originalValue1->expects($this->once())->method('setData')->with('is_delete', 1); + $originalValue2->expects($this->once())->method('getData')->with('option_type_id')->willReturn(4); + $originalValue3->expects($this->once())->method('getData')->with('option_type_id')->willReturn(5); + + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([]); + $this->optionMock->expects($this->once())->method('getData')->with('values')->willReturn([ + ['option_type_id' => 4], + ['option_type_id' => 5] + ]); + $optionCollection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Option\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $optionCollection->expects($this->once())->method('getProductOptions')->willReturn([$this->optionMock]); + $this->optionCollectionFactory->expects($this->once())->method('create')->willReturn($optionCollection); + $this->optionMock->expects($this->once())->method('getValues')->willReturn([ + $originalValue1, + $originalValue2, + $originalValue3 + ]); + $this->assertEquals($this->optionMock, $this->optionRepository->save($this->optionMock)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php new file mode 100644 index 0000000000000..cf29f44550f1f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php @@ -0,0 +1,72 @@ +entity = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionRepository = $this->getMockBuilder(Repository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new SaveHandler($this->optionRepository); + } + + public function testExecute() + { + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn(5); + $this->entity->expects($this->once())->method('getOptions')->willReturn([$this->optionMock]); + + $secondOptionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $secondOptionMock->expects($this->once())->method('getOptionId')->willReturn(6); + + $this->optionRepository + ->expects($this->once()) + ->method('getProductOptions') + ->with($this->entity) + ->willReturn([$this->optionMock, $secondOptionMock]); + + $this->optionRepository->expects($this->once())->method('delete'); + $this->optionRepository->expects($this->once())->method('save')->with($this->optionMock); + + $this->assertEquals($this->entity, $this->model->execute($this->entity)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FactoryTest.php index 1b66800ca8c01..d9b381ec3a365 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FactoryTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->rootDirectory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() - ->setMethods(['isFile', 'isReadable', 'getAbsolutePath']) - ->getMockForAbstractClass(); + ->getMock(); + + $this->rootDirectory = $this->getMockBuilder(ReadInterface::class) + ->getMock(); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->with(DirectoryList::MEDIA, DriverPool::FILE) + ->willReturn($this->rootDirectory); + + $this->serializer = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->urlBuilder = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option\UrlBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class) + ->disableOriginalConstructor() + ->getMock(); $this->coreFileStorageDatabase = $this->getMock( \Magento\MediaStorage\Helper\File\Storage\Database::class, @@ -48,24 +98,65 @@ protected function getFileObject() return $this->objectManager->getObject( \Magento\Catalog\Model\Product\Option\Type\File::class, [ - 'saleableItem' => $this->rootDirectory, - 'priceCurrency' => $this->coreFileStorageDatabase + 'filesystem' => $this->filesystemMock, + 'coreFileStorageDatabase' => $this->coreFileStorageDatabase, + 'serializer' => $this->serializer, + 'urlBuilder' => $this->urlBuilder, + 'escaper' => $this->escaper ] ); } + public function testGetCustomizedView() + { + $fileObject = $this->getFileObject(); + $optionInfo = ['option_value' => 'some serialized data']; + + $dataAfterSerialize = ['some' => 'array']; + + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with('some serialized data') + ->willReturn($dataAfterSerialize); + + $this->urlBuilder->expects($this->once()) + ->method('getUrl') + ->willReturn('someUrl'); + + $this->escaper->expects($this->once()) + ->method('escapeHtml') + ->willReturn('string'); + + $this->assertEquals( + 'string ', + $fileObject->getCustomizedView($optionInfo) + ); + } + public function testCopyQuoteToOrder() { - $optionMock = $this->getMockBuilder( - \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class - )->disableOriginalConstructor()->setMethods(['getValue'])->getMockForAbstractClass(); + $optionMock = $this->getMockBuilder(OptionInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getValue']) + ->getMockForAbstractClass(); $quotePath = '/quote/path/path/uploaded.file'; $orderPath = '/order/path/path/uploaded.file'; + $quoteValue = "{\"quote_path\":\"$quotePath\",\"order_path\":\"$orderPath\"}"; + + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with($quoteValue) + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + $optionMock->expects($this->any()) ->method('getValue') - ->will($this->returnValue(['quote_path' => $quotePath, 'order_path' => $orderPath])); + ->will($this->returnValue($quoteValue)); $this->rootDirectory->expects($this->any()) ->method('isFile') @@ -93,4 +184,55 @@ public function testCopyQuoteToOrder() $fileObject->copyQuoteToOrder() ); } + + public function testGetFormattedOptionValue() + { + $resultValue = ['result']; + $optionValue = json_encode($resultValue); + $urlParameter = 'parameter'; + + $fileObject = $this->getFileObject(); + $fileObject->setCustomOptionUrlParams($urlParameter); + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with($optionValue) + ->willReturn($resultValue); + + $resultValue['url'] = [ + 'route' => 'sales/download/downloadCustomOption', + 'params' => $fileObject->getCustomOptionUrlParams() + ]; + + $this->serializer->expects($this->once()) + ->method('serialize') + ->with($resultValue) + ->willReturn(json_encode($resultValue)); + + $option = $this->getMockBuilder(\Magento\Quote\Model\Quote\Item\Option::class) + ->setMethods(['setValue']) + ->disableOriginalConstructor() + ->getMock(); + + $option->expects($this->once()) + ->method('setValue') + ->with(json_encode($resultValue)); + + $fileObject->setConfigurationItemOption($option); + + $fileObject->getFormattedOptionValue($optionValue); + } + + public function testPrepareOptionValueForRequest() + { + $optionValue = 'string'; + $resultValue = ['result']; + $fileObject = $this->getFileObject(); + + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with($optionValue) + ->willReturn($resultValue); + + $this->assertEquals($resultValue, $fileObject->prepareOptionValueForRequest($optionValue)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/UrlBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/UrlBuilderTest.php index 9b0e1e1627454..0d543d16286d7 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/UrlBuilderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/UrlBuilderTest.php @@ -1,6 +1,6 @@ pricePersistenceFactory = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Price\PricePersistenceFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->pricePersistence = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\PricePersistence::class) + ->disableOriginalConstructor() + ->getMock(); + $this->basePriceInterfaceFactory = $this->getMockBuilder( + \Magento\Catalog\Api\Data\BasePriceInterfaceFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->basePriceInterface = $this->getMockBuilder(\Magento\Catalog\Api\Data\BasePriceInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeRepository = $this->getMockBuilder(\Magento\Store\Api\StoreRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->invalidSkuProcessor = $this + ->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validationResult = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\BasePriceStorage::class, + [ + 'pricePersistenceFactory' => $this->pricePersistenceFactory, + 'basePriceInterfaceFactory' => $this->basePriceInterfaceFactory, + 'productIdLocator' => $this->productIdLocator, + 'storeRepository' => $this->storeRepository, + 'invalidSkuProcessor' => $this->invalidSkuProcessor, + 'validationResult' => $this->validationResult, + 'allowedProductTypes' => ['simple', 'virtual', 'bundle', 'downloadable'], + ] + ); + } + + /** + * Test get method. + * + * @return void + */ + public function testGet() + { + $skus = ['sku_1', 'sku_2', 'sku_3']; + $validSkus = ['sku_1', 'sku_2']; + $rawPrices = [ + [ + 'row_id' => 1, + 'value' => 15, + 'store_id' => 1 + ], + [ + 'row_id' => 2, + 'value' => 35, + 'store_id' => 1 + ] + ]; + $this->invalidSkuProcessor->expects($this->once()) + ->method('filterSkuList') + ->with($skus, ['simple', 'virtual', 'bundle', 'downloadable'], 1) + ->willReturn($validSkus); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'price']) + ->willReturn($this->pricePersistence); + $this->pricePersistence->expects($this->once())->method('get')->with($validSkus)->willReturn($rawPrices); + $this->basePriceInterfaceFactory + ->expects($this->atLeastOnce()) + ->method('create') + ->willReturn($this->basePriceInterface); + $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id'); + $this->pricePersistence + ->expects($this->atLeastOnce()) + ->method('retrieveSkuById') + ->willReturnOnConsecutiveCalls('sku_1', 'sku_2'); + $this->basePriceInterface + ->expects($this->atLeastOnce()) + ->method('setSku') + ->withConsecutive(['sku_1'], ['sku_2']) + ->willReturnSelf(); + $this->basePriceInterface + ->expects($this->atLeastOnce()) + ->method('setPrice') + ->withConsecutive([15], [35]) + ->willReturnSelf(); + $this->basePriceInterface + ->expects($this->atLeastOnce()) + ->method('setStoreId') + ->withConsecutive([1], [1]) + ->willReturnSelf(); + + $this->model->get($skus); + } + + /** + * Test update method. + * + * @return void + */ + public function testUpdate() + { + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $sku = 'sku_1'; + $idsBySku = [ + 'sku_1' => + [ + 1 => [ + $this->basePriceInterface + ] + ] + ]; + $this->basePriceInterface->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); + $this->invalidSkuProcessor->expects($this->once()) + ->method('retrieveInvalidSkuList') + ->with([1 => $sku], ['simple', 'virtual', 'bundle', 'downloadable'], 1) + ->willReturn([]); + $this->basePriceInterface->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $this->basePriceInterface->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $this->validationResult->expects($this->once())->method('getFailedRowIds')->willReturn([]); + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus')->with([$sku]) + ->willReturn($idsBySku); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'price']) + ->willReturn($this->pricePersistence); + $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id'); + $this->storeRepository->expects($this->once())->method('getById')->with(1)->willReturn($store); + $formattedPrices = [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15 + ] + ]; + $this->pricePersistence->expects($this->once())->method('update')->with($formattedPrices); + $this->validationResult->expects($this->any())->method('getFailedItems')->willReturn([]); + $this->assertEquals([], $this->model->update([1 => $this->basePriceInterface])); + } + + /** + * Test update method without SKU and with negative price. + * + * @return void + */ + public function testUpdateWithoutSkuAndWithNegativePrice() + { + $exception = new \Magento\Framework\Exception\NoSuchEntityException(); + $this->basePriceInterface->expects($this->atLeastOnce())->method('getSku')->willReturn(null); + $this->basePriceInterface->expects($this->atLeastOnce())->method('getPrice')->willReturn(-10); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'price']) + ->willReturn($this->pricePersistence); + $this->invalidSkuProcessor->expects($this->once()) + ->method('retrieveInvalidSkuList') + ->with([null], ['simple', 'virtual', 'bundle', 'downloadable'], 1) + ->willReturn([]); + $priceUpdateResult = $this->getMockBuilder(\Magento\Catalog\Api\Data\PriceUpdateResultInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->validationResult->expects($this->atLeastOnce()) + ->method('addFailedItem') + ->withConsecutive( + [ + 0, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'SKU', 'fieldValue' => null] + ], + [ + 0, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'Price', 'fieldValue' => -10] + ], + [ + 0, + __( + 'Requested store is not found. Row ID: SKU = %SKU, Store ID: %storeId.', + ['SKU' => null, 'storeId' => 10] + ), + ['SKU' => null, 'storeId' => 10] + ] + ); + $this->basePriceInterface->expects($this->atLeastOnce())->method('getStoreId')->willReturn(10); + $this->storeRepository->expects($this->once())->method('getById')->with(10)->willThrowException($exception); + $this->validationResult->expects($this->once())->method('getFailedRowIds')->willReturn([0 => 0]); + $this->pricePersistence->expects($this->once())->method('update')->with([]); + $this->validationResult->expects($this->once())->method('getFailedItems')->willReturn([$priceUpdateResult]); + + $this->assertEquals( + [$priceUpdateResult], + $this->model->update([$this->basePriceInterface]) + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php new file mode 100644 index 0000000000000..6efd2281b96a0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php @@ -0,0 +1,311 @@ +pricePersistenceFactory = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Price\PricePersistenceFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->pricePersistence = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\PricePersistence::class) + ->disableOriginalConstructor() + ->getMock(); + $this->costInterfaceFactory = $this->getMockBuilder(\Magento\Catalog\Api\Data\CostInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->costInterface = $this->getMockBuilder(\Magento\Catalog\Api\Data\CostInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeRepository = $this->getMockBuilder(\Magento\Store\Api\StoreRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->validationResult = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invalidSkuProcessor = $this + ->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\CostStorage::class, + [ + 'pricePersistenceFactory' => $this->pricePersistenceFactory, + 'costInterfaceFactory' => $this->costInterfaceFactory, + 'productIdLocator' => $this->productIdLocator, + 'storeRepository' => $this->storeRepository, + 'validationResult' => $this->validationResult, + 'invalidSkuProcessor' => $this->invalidSkuProcessor, + 'allowedProductTypes' => ['simple', 'virtual', 'downloadable'], + ] + ); + } + + /** + * Test get method. + * + * @return void + */ + public function testGet() + { + $skus = ['sku_1', 'sku_2']; + $rawPrices = [ + [ + 'row_id' => 1, + 'value' => 15, + 'store_id' => 1 + ], + [ + 'row_id' => 2, + 'value' => 35, + 'store_id' => 1 + ] + ]; + $this->invalidSkuProcessor + ->expects($this->once()) + ->method('filterSkuList') + ->with($skus, ['simple', 'virtual', 'downloadable']) + ->willReturn($skus); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'cost']) + ->willReturn($this->pricePersistence); + $this->pricePersistence->expects($this->once())->method('get')->with($skus)->willReturn($rawPrices); + $this->costInterfaceFactory + ->expects($this->atLeastOnce()) + ->method('create') + ->willReturn($this->costInterface); + $this->pricePersistence + ->expects($this->atLeastOnce()) + ->method('retrieveSkuById') + ->willReturnOnConsecutiveCalls('sku_1', 'sku_2'); + $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id'); + $this->costInterface + ->expects($this->atLeastOnce()) + ->method('setSku') + ->withConsecutive(['sku_1'], ['sku_2']) + ->willReturnSelf(); + $this->costInterface + ->expects($this->atLeastOnce()) + ->method('setCost') + ->withConsecutive([15], [35]) + ->willReturnSelf(); + $this->costInterface + ->expects($this->atLeastOnce()) + ->method('setStoreId') + ->withConsecutive([1], [1]) + ->willReturnSelf(); + + $this->model->get($skus); + } + + /** + * Test update method. + * + * @return void + */ + public function testUpdate() + { + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $sku = 'sku_1'; + $idsBySku = [ + 'sku_1' => + [ + 1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL + ] + ]; + $this->costInterface->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); + $this->invalidSkuProcessor + ->expects($this->once()) + ->method('retrieveInvalidSkuList') + ->willReturn([]); + $this->costInterface->expects($this->atLeastOnce())->method('getCost')->willReturn(15); + $this->costInterface->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $this->validationResult + ->expects($this->once()) + ->method('getFailedRowIds') + ->willReturn([]); + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus')->with([$sku]) + ->willReturn($idsBySku); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'cost']) + ->willReturn($this->pricePersistence); + $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id'); + $this->storeRepository->expects($this->once())->method('getById')->with(1)->willReturn($store); + $formattedPrices = [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15 + ] + ]; + $this->pricePersistence->expects($this->once())->method('update')->with($formattedPrices); + $this->validationResult + ->expects($this->once()) + ->method('getFailedItems') + ->willReturn([]); + + $this->assertEmpty($this->model->update([$this->costInterface])); + } + + /** + * Test update method with negative cost and without SKU. + * + * @return void + */ + public function testUpdateWithNegativeCostAndWithoutSku() + { + $exception = new \Magento\Framework\Exception\NoSuchEntityException(); + $this->costInterface->expects($this->atLeastOnce())->method('getSku')->willReturn(null); + $this->costInterface->expects($this->atLeastOnce())->method('getCost')->willReturn(-15); + $this->costInterface->expects($this->atLeastOnce())->method('getStoreId')->willReturn(10); + $this->validationResult->expects($this->once())->method('getFailedRowIds')->willReturn([0 => 0]); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'cost']) + ->willReturn($this->pricePersistence); + $priceUpdateResult = $this->getMockBuilder(\Magento\Catalog\Api\Data\PriceUpdateResultInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->validationResult->expects($this->atLeastOnce()) + ->method('addFailedItem') + ->withConsecutive( + [ + 0, + __( + 'Invalid attribute %fieldName = %fieldValue.', + ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue'] + ), + ['fieldName' => 'SKU', 'fieldValue' => null] + ], + [ + 0, + __( + 'Invalid attribute Cost = %cost. Row ID: SKU = %SKU, Store ID: %storeId.', + ['cost' => -15, 'SKU' => null, 'storeId' => 10] + ), + ['cost' => -15, 'SKU' => null, 'storeId' => 10] + ], + [ + 0, + __( + 'Requested store is not found. Row ID: SKU = %SKU, Store ID: %storeId.', + ['SKU' => null, 'storeId' => 10] + ), + ['SKU' => null, 'storeId' => 10] + ] + ); + $this->storeRepository->expects($this->once())->method('getById')->with(10)->willThrowException($exception); + $this->invalidSkuProcessor + ->expects($this->once()) + ->method('retrieveInvalidSkuList') + ->willReturn([]); + $this->pricePersistence->expects($this->once())->method('update')->with([]); + $this->validationResult->expects($this->once())->method('getFailedItems')->willReturn([$priceUpdateResult]); + + $this->assertEquals( + [$priceUpdateResult], + $this->model->update([$this->costInterface]) + ); + } + + /** + * Test delete method. + * + * @return void + */ + public function testDelete() + { + $skus = ['sku_1', 'sku_2']; + $this->invalidSkuProcessor + ->expects($this->once()) + ->method('filterSkuList') + ->with($skus, ['simple', 'virtual', 'downloadable']) + ->willReturn($skus); + $this->pricePersistenceFactory + ->expects($this->once()) + ->method('create') + ->with(['attributeCode' => 'cost']) + ->willReturn($this->pricePersistence); + $this->pricePersistence->expects($this->once())->method('delete')->with($skus); + + $this->model->delete($skus); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php new file mode 100644 index 0000000000000..3278bd9f51c10 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php @@ -0,0 +1,365 @@ +attributeResource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Attribute::class) + ->disableOriginalConstructor()->getMock(); + $this->attributeRepository = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor() + ->setMethods(['getLinkField', 'getMetadata']) + ->getMock(); + $this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->productAttribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\PricePersistence::class, + [ + 'attributeResource' => $this->attributeResource, + 'attributeRepository' => $this->attributeRepository, + 'productIdLocator' => $this->productIdLocator, + 'metadataPool' => $this->metadataPool, + ] + ); + } + + /** + * Test get method. + * + * @return void + */ + public function testGet() + { + $attributeId = 5; + $skus = ['sku_1', 'sku_2']; + $idsBySku = [ + 'sku_1' => + [ + 1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE + ], + 'sku_2' => + [ + 2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL + ] + ]; + $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor()->getMock(); + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus')->with($skus) + ->willReturn($idsBySku); + $this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection); + $this->connection->expects($this->once())->method('select')->willReturn($select); + $this->attributeResource + ->expects($this->once()) + ->method('getTable') + ->with('catalog_product_entity_decimal') + ->willReturn('catalog_product_entity_decimal'); + $select->expects($this->once())->method('from')->with('catalog_product_entity_decimal')->willReturnSelf(); + $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); + $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $select + ->expects($this->atLeastOnce()) + ->method('where') + ->withConsecutive(['row_id IN (?)', [1, 2]], ['attribute_id = ?', $attributeId]) + ->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id'); + $this->model->get($skus); + } + + /** + * Test update method. + * + * @return void + */ + public function testUpdate() + { + $attributeId = 5; + $prices = [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15 + ] + ]; + $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); + $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection); + $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $this->attributeResource + ->expects($this->once()) + ->method('getTable') + ->with('catalog_product_entity_decimal') + ->willReturn('catalog_product_entity_decimal'); + $this->connection + ->expects($this->once()) + ->method('insertOnDuplicate') + ->with( + 'catalog_product_entity_decimal', + [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15, + 'attribute_id' => 5, + ] + ], + ['value'] + ) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('commit')->willReturnSelf(); + $this->model->update($prices); + } + + /** + * Test update method throws exception. + * + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage Could not save Prices. + */ + public function testUpdateWithException() + { + $attributeId = 5; + $prices = [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15 + ] + ]; + $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); + $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection); + $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $this->attributeResource + ->expects($this->once()) + ->method('getTable') + ->with('catalog_product_entity_decimal') + ->willReturn('catalog_product_entity_decimal'); + $this->connection + ->expects($this->once()) + ->method('insertOnDuplicate') + ->with( + 'catalog_product_entity_decimal', + [ + [ + 'store_id' => 1, + 'row_id' => 1, + 'value' => 15, + 'attribute_id' => 5, + ] + ], + ['value'] + ) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('commit')->willThrowException(new \Exception()); + $this->connection->expects($this->once())->method('rollback')->willReturnSelf(); + $this->model->update($prices); + } + + /** + * Test delete method. + * + * @return void + */ + public function testDelete() + { + $attributeId = 5; + $skus = ['sku_1', 'sku_2']; + $idsBySku = [ + 'sku_1' => + [ + 1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE + ], + 'sku_2' => + [ + 2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL + ] + ]; + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus')->with($skus) + ->willReturn($idsBySku); + $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); + $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->attributeResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connection); + $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $this->attributeResource + ->expects($this->once()) + ->method('getTable') + ->with('catalog_product_entity_decimal') + ->willReturn('catalog_product_entity_decimal'); + $this->connection + ->expects($this->once()) + ->method('delete') + ->with( + 'catalog_product_entity_decimal', + [ + 'attribute_id = ?' => $attributeId, + 'row_id IN (?)' => [1, 2] + ] + ) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('commit')->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id'); + $this->model->delete($skus); + } + + /** + * Test delete method throws exception. + * + * @expectedException \Magento\Framework\Exception\CouldNotDeleteException + * @expectedExceptionMessage Could not delete Prices + */ + public function testDeleteWithException() + { + $attributeId = 5; + $skus = ['sku_1', 'sku_2']; + $idsBySku = [ + 'sku_1' => + [ + 1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE + ], + 'sku_2' => + [ + 2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL + ] + ]; + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus')->with($skus) + ->willReturn($idsBySku); + $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute); + $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->attributeResource->expects($this->atLeastOnce(2))->method('getConnection') + ->willReturn($this->connection); + $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $this->attributeResource + ->expects($this->once()) + ->method('getTable') + ->with('catalog_product_entity_decimal') + ->willReturn('catalog_product_entity_decimal'); + $this->connection + ->expects($this->once()) + ->method('delete') + ->with( + 'catalog_product_entity_decimal', + [ + 'attribute_id = ?' => $attributeId, + 'row_id IN (?)' => [1, 2] + ] + ) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('commit')->willThrowException(new \Exception()); + $this->connection->expects($this->once())->method('rollBack')->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf(); + $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id'); + $this->model->delete($skus); + } + + /** + * Test retrieveSkuById method. + * + * @param int|null $expectedResult + * @param int $id + * @param array $skus + * @dataProvider dataProviderRetrieveSkuById + */ + public function testRetrieveSkuById($expectedResult, $id, array $skus) + { + $this->productIdLocator + ->expects($this->once()) + ->method('retrieveProductIdsBySkus') + ->willReturn($skus); + + $this->assertEquals($expectedResult, $this->model->retrieveSkuById($id, $skus)); + } + + /** + * Data provider for retrieveSkuById method. + * + * @return array + */ + public function dataProviderRetrieveSkuById() + { + return [ + [ + null, + 2, + ['sku_1' => [1 => 1]] + ], + [ + 'sku_1', + 1, + ['sku_1' => [1 => 1]] + ], + [ + null, + 1, + ['sku_1' => [2 => 1]] + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/SpecialPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/SpecialPriceStorageTest.php new file mode 100644 index 0000000000000..d71856be51eba --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/SpecialPriceStorageTest.php @@ -0,0 +1,385 @@ +specialPriceResource = $this->getMockBuilder(\Magento\Catalog\Api\SpecialPriceInterface::class) + ->disableOriginalConstructor()->setMethods(['get', 'update', 'delete', 'getEntityLinkField'])->getMock(); + $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->storeRepository = $this->getMockBuilder(\Magento\Store\Api\StoreRepositoryInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->invalidSkuProcessor = $this + ->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor::class) + ->disableOriginalConstructor()->getMock(); + $this->validationResult = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor()->getMock(); + $this->specialPriceFactory = $this->getMockBuilder( + \Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory::class + )->disableOriginalConstructor()->setMethods(['create'])->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\SpecialPriceStorage::class, + [ + 'specialPriceResource' => $this->specialPriceResource, + 'specialPriceFactory' => $this->specialPriceFactory, + 'productIdLocator' => $this->productIdLocator, + 'storeRepository' => $this->storeRepository, + 'invalidSkuProcessor' => $this->invalidSkuProcessor, + 'validationResult' => $this->validationResult, + ] + ); + } + + /** + * Test get method. + * + * @return void + */ + public function testGet() + { + $skus = ['sku_1', 'sku_2']; + $rawPrices = [ + [ + 'entity_id' => 1, + 'value' => 15, + 'store_id' => 1, + 'sku' => 'sku_1', + 'price_from' => '2016-12-20 01:02:03', + 'price_to' => '2016-12-21 01:02:03', + ], + [ + 'entity_id' => 2, + 'value' => 15, + 'store_id' => 1, + 'price_from' => '2016-12-20 01:02:03', + 'price_to' => '2016-12-21 01:02:03', + ], + [ + 'entity_id' => 3, + 'value' => 15, + 'store_id' => 1, + 'price_from' => '2016-12-20 01:02:03', + 'price_to' => '2016-12-21 01:02:03', + ], + ]; + $this->invalidSkuProcessor->expects($this->once())->method('filterSkuList')->with($skus, [])->willReturn($skus); + $this->specialPriceResource->expects($this->once())->method('get')->willReturn($rawPrices); + $this->specialPriceResource->expects($this->atLeastOnce()) + ->method('getEntityLinkField')->willReturn('entity_id'); + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $price->expects($this->exactly(3))->method('setPrice'); + $this->specialPriceFactory->expects($this->atLeastOnce())->method('create')->willReturn($price); + $this->productIdLocator->expects($this->atLeastOnce())->method('retrieveProductIdsBySkus')->willReturn( + [ + 'sku_2' => [2 => 'prod'] + ] + ); + $this->model->get($skus); + } + + /** + * Test update method. + * + * @return void + */ + public function testUpdate() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('2016-12-20 01:02:03'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('2016-12-21 01:02:03'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn([]); + $this->storeRepository->expects($this->once())->method('getById'); + $this->validationResult->expects($this->never())->method('addFailedItem'); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $this->specialPriceResource->expects($this->once())->method('update')->with($prices); + + $this->model->update($prices); + } + + /** + * Test update method with invalid sku. + * + * @return void + */ + public function testUpdateWithInvalidSku() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('2016-12-20 01:02:03'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('2016-12-21 01:02:03'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn(['sku_1']); + $this->storeRepository->expects($this->once())->method('getById'); + $this->validationResult + ->expects($this->once()) + ->method('addFailedItem') + ->with( + 1, + __( + 'Requested product doesn\'t exist. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ), + [ + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([1]); + $this->specialPriceResource->expects($this->once())->method('update')->with([]); + + $this->model->update($prices); + } + + /** + * Test update method with price = null. + * + * @return void + */ + public function testUpdateWithoutPrice() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(null); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('2016-12-20 01:02:03'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('2016-12-21 01:02:03'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn([]); + $this->storeRepository->expects($this->once())->method('getById'); + $this->validationResult->expects($this->once()) + ->method('addFailedItem') + ->with( + 1, + __( + 'Invalid attribute Price = %price. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'price' => null, + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ), + [ + 'price' => null, + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([1]); + $this->specialPriceResource->expects($this->once())->method('update')->with([]); + + $this->model->update($prices); + } + + /** + * Test update method with price = null. + * + * @return void + */ + public function testUpdateWithException() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('2016-12-20 01:02:03'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('2016-12-21 01:02:03'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn([]); + $this->storeRepository->expects($this->once())->method('getById') + ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException()); + $this->validationResult->expects($this->once()) + ->method('addFailedItem') + ->with( + 1, + __( + 'Requested store is not found. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ), + [ + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-20 01:02:03', + 'priceTo' => '2016-12-21 01:02:03' + ] + ); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([1]); + $this->specialPriceResource->expects($this->once())->method('update')->with([]); + + $this->model->update($prices); + } + + /** + * Test update method with incorrect price_from field. + * + * @return void + */ + public function testUpdateWithIncorrectPriceFrom() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('incorrect'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('2016-12-21 01:02:03'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn([]); + $this->storeRepository->expects($this->once())->method('getById'); + $this->validationResult->expects($this->once()) + ->method('addFailedItem') + ->with( + 1, + __( + 'Invalid attribute %label = %priceTo. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'label' => 'Price From', + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => 'incorrect', + 'priceTo' => '2016-12-21 01:02:03' + ] + ), + [ + 'label' => 'Price From', + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => 'incorrect', + 'priceTo' => '2016-12-21 01:02:03' + ] + ); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([1]); + $this->specialPriceResource->expects($this->once())->method('update')->with([]); + + $this->model->update($prices); + } + + /** + * Test update method with incorrect price_to field. + * + * @return void + */ + public function testUpdateWithIncorrectPriceTo() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\SpecialPriceInterface::class) + ->disableOriginalConstructor()->getMock(); + $prices = [1 => $price]; + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('sku_1'); + $price->expects($this->atLeastOnce())->method('getPrice')->willReturn(15); + $price->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); + $price->expects($this->atLeastOnce())->method('getPriceFrom')->willReturn('2016-12-21 01:02:03'); + $price->expects($this->atLeastOnce())->method('getPriceTo')->willReturn('incorrect'); + $this->invalidSkuProcessor->expects($this->once())->method('retrieveInvalidSkuList')->willReturn([]); + $this->storeRepository->expects($this->once())->method('getById'); + $this->validationResult->expects($this->once()) + ->method('addFailedItem') + ->with( + 1, + __( + 'Invalid attribute %label = %priceTo. ' + . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.', + [ + 'label' => 'Price To', + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-21 01:02:03', + 'priceTo' => 'incorrect' + ] + ), + [ + 'label' => 'Price To', + 'SKU' => 'sku_1', + 'storeId' => 1, + 'priceFrom' => '2016-12-21 01:02:03', + 'priceTo' => 'incorrect' + ] + ); + $this->validationResult->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([1]); + $this->specialPriceResource->expects($this->once())->method('update')->with([]); + + $this->model->update($prices); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php new file mode 100644 index 0000000000000..7c90444f90b58 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php @@ -0,0 +1,300 @@ +tierPricePersistence = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Price\TierPricePersistence::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->tierPricePersistence->expects($this->any()) + ->method('getEntityLinkField') + ->willReturn('row_id'); + $this->tierPriceValidator = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->tierPriceFactory = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Price\TierPriceFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->priceIndexer = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->config = $this->getMockBuilder(\Magento\PageCache\Model\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->typeList = $this->getMockBuilder(\Magento\Framework\App\Cache\TypeListInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->tierPriceStorage = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\TierPriceStorage::class, + [ + 'tierPricePersistence' => $this->tierPricePersistence, + 'tierPriceValidator' => $this->tierPriceValidator, + 'tierPriceFactory' => $this->tierPriceFactory, + 'priceIndexer' => $this->priceIndexer, + 'productIdLocator' => $this->productIdLocator, + 'config' => $this->config, + 'typeList' => $this->typeList, + ] + ); + } + + /** + * Test get method. + * + * @return void + */ + public function testGet() + { + $skus = ['simple', 'virtual']; + $this->tierPriceValidator + ->expects($this->once()) + ->method('validateSkus') + ->with($skus) + ->willReturn($skus); + $this->productIdLocator->expects($this->atLeastOnce()) + ->method('retrieveProductIdsBySkus') + ->with(['simple', 'virtual']) + ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); + $this->tierPricePersistence->expects($this->once()) + ->method('get') + ->willReturn( + [ + [ + 'value_id' => 1, + 'row_id' => 2, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 2.0000, + 'value' => 2.0000, + 'percentage_value' => null, + 'website_id' => 0 + ], + [ + 'value_id' => 2, + 'row_id' => 3, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 3.0000, + 'value' => 3.0000, + 'percentage_value' => null, + 'website_id' => 0 + ] + ] + ); + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); + $this->tierPriceFactory->expects($this->atLeastOnce())->method('create')->willReturn($price); + $prices = $this->tierPriceStorage->get($skus); + $this->assertNotEmpty($prices); + $this->assertEquals(2, count($prices)); + } + + /** + * Test update method. + * + * @return void + */ + public function testUpdate() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); + $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor() + ->getMock(); + $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $this->productIdLocator->expects($this->atLeastOnce()) + ->method('retrieveProductIdsBySkus') + ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); + $this->tierPriceValidator + ->expects($this->atLeastOnce()) + ->method('retrieveValidationResult') + ->willReturn($result); + $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( + [ + 'row_id' => 2, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 2, + 'value' => 3, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); + $this->tierPricePersistence->expects($this->once()) + ->method('get') + ->willReturn( + [ + [ + 'value_id' => 1, + 'row_id' => 2, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 2.0000, + 'value' => 2.0000, + 'percentage_value' => null, + 'website_id' => 0 + ] + ] + ); + $this->tierPricePersistence->expects($this->atLeastOnce())->method('update'); + $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); + $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); + $this->typeList->expects($this->atLeastOnce())->method('invalidate'); + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple'); + $this->assertEmpty($this->tierPriceStorage->update([$price])); + } + + /** + * Test replace method. + * + * @return void + */ + public function testReplace() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('virtual'); + $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor() + ->getMock(); + $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $this->productIdLocator->expects($this->atLeastOnce()) + ->method('retrieveProductIdsBySkus') + ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); + + $this->tierPriceValidator + ->expects($this->atLeastOnce()) + ->method('retrieveValidationResult') + ->willReturn($result); + $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( + [ + 'row_id' => 3, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 3, + 'value' => 7, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); + $this->tierPricePersistence->expects($this->atLeastOnce())->method('replace'); + $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); + $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); + $this->typeList->expects($this->atLeastOnce())->method('invalidate'); + $this->assertEmpty($this->tierPriceStorage->replace([$price])); + } + + /** + * Test delete method. + * + * @return void + */ + public function testDelete() + { + $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); + $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple'); + $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor() + ->getMock(); + $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $this->tierPriceValidator->expects($this->atLeastOnce()) + ->method('retrieveValidationResult') + ->willReturn($result); + $this->productIdLocator->expects($this->atLeastOnce()) + ->method('retrieveProductIdsBySkus') + ->willReturn(['simple' => ['2' => 'simple']]); + $this->tierPricePersistence->expects($this->once()) + ->method('get') + ->willReturn( + [ + [ + 'value_id' => 7, + 'row_id' => 7, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 5.0000, + 'value' => 6.0000, + 'percentage_value' => null, + 'website_id' => 0 + ] + ] + ); + $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( + [ + 'row_id' => 3, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 3, + 'value' => 7, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); + $this->tierPricePersistence->expects($this->atLeastOnce())->method('delete'); + $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); + $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); + $this->typeList->expects($this->atLeastOnce())->method('invalidate'); + $this->assertEmpty($this->tierPriceStorage->delete([$price])); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/InvalidSkuProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/InvalidSkuProcessorTest.php new file mode 100644 index 0000000000000..3a79f7a48944e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/InvalidSkuProcessorTest.php @@ -0,0 +1,89 @@ +productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->productRepository = $this->getMockBuilder(\Magento\Catalog\Api\ProductRepositoryInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->invalidSkuProcessor = $objectManager->getObject( + \Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor::class, + [ + 'productIdLocator' => $this->productIdLocator, + 'productRepository' => $this->productRepository + ] + ); + } + + /** + * Prepare retrieveInvalidSkuList(). + * + * @param string $productType + * @param string $productSku + * @return void + */ + private function prepareRetrieveInvalidSkuListMethod($productType, $productSku) + { + $idsBySku = [$productSku => [235235235 => $productType]]; + $this->productIdLocator->expects($this->atLeastOnce())->method('retrieveProductIdsBySkus') + ->willReturn($idsBySku); + $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) + ->setMethods(['getPriceType']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $productPriceType = 0; + $product->expects($this->atLeastOnce())->method('getPriceType')->willReturn($productPriceType); + $this->productRepository->expects($this->atLeastOnce())->method('get')->willReturn($product); + } + + /** + * Test for retrieveInvalidSkuList(). + * + * @return void + */ + public function testRetrieveInvalidSkuList() + { + $productSku = 'LKJKJ2233636'; + $productType = \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE; + $methodParamSku = 'SDFSDF3242355'; + $skus = [$methodParamSku]; + $allowedProductTypes = [$productType]; + $allowedPriceTypeValue = true; + $this->prepareRetrieveInvalidSkuListMethod($productType, $productSku); + + $this->assertEquals( + [$methodParamSku, $productSku], + $this->invalidSkuProcessor->retrieveInvalidSkuList($skus, $allowedProductTypes, $allowedPriceTypeValue) + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/TierPriceValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/TierPriceValidatorTest.php new file mode 100644 index 0000000000000..9274feed348b6 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/Validation/TierPriceValidatorTest.php @@ -0,0 +1,260 @@ +productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->searchCriteriaBuilder = $this->getMockBuilder(\Magento\Framework\Api\SearchCriteriaBuilder::class) + ->disableOriginalConstructor()->getMock(); + $this->filterBuilder = $this->getMockBuilder(\Magento\Framework\Api\FilterBuilder::class) + ->disableOriginalConstructor()->getMock(); + $this->customerGroupRepository = $this->getMockBuilder(\Magento\Customer\Api\GroupRepositoryInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->websiteRepository = $this->getMockBuilder(\Magento\Store\Api\WebsiteRepositoryInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->validationResult = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) + ->disableOriginalConstructor()->getMock(); + $this->invalidSkuProcessor = $this + ->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor::class) + ->disableOriginalConstructor()->getMock(); + $this->tierPrice = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->tierPriceValidator = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator::class, + [ + 'productIdLocator' => $this->productIdLocator, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilder, + 'filterBuilder' => $this->filterBuilder, + 'customerGroupRepository' => $this->customerGroupRepository, + 'websiteRepository' => $this->websiteRepository, + 'validationResult' => $this->validationResult, + 'invalidSkuProcessor' => $this->invalidSkuProcessor + ] + ); + } + + /** + * Prepare CustomerGroupRepository mock. + * + * @param array $returned + * @return void + */ + private function prepareCustomerGroupRepositoryMock(array $returned) + { + $searchCriteria = $this + ->getMockBuilder(\Magento\Framework\Api\Search\SearchCriteriaInterface::class) + ->disableOriginalConstructor()->getMock(); + $filter = $this->getMockBuilder(\Magento\Framework\Api\AbstractSimpleObject::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->filterBuilder->expects($this->atLeastOnce())->method('setField')->willReturnSelf(); + $this->filterBuilder->expects($this->atLeastOnce())->method('setValue')->willReturnSelf(); + $this->filterBuilder->expects($this->atLeastOnce())->method('create')->willReturn($filter); + $this->searchCriteriaBuilder->expects($this->atLeastOnce())->method('addFilters')->willReturnSelf(); + $this->searchCriteriaBuilder->expects($this->atLeastOnce())->method('create')->willReturn($searchCriteria); + $customerGroupSearchResults = $this + ->getMockBuilder(\Magento\Customer\Api\Data\GroupSearchResultsInterface::class) + ->disableOriginalConstructor()->getMock(); + $customerGroupSearchResults->expects($this->once())->method('getItems') + ->willReturn($returned['customerGroupSearchResults_getItems']); + $this->customerGroupRepository->expects($this->atLeastOnce())->method('getList') + ->willReturn($customerGroupSearchResults); + } + + /** + * Prepare retrieveValidationResult(). + * + * @param string $sku + * @param array $returned + * @return void + */ + private function prepareRetrieveValidationResultMethod($sku, array $returned) + { + $this->tierPrice->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); + $tierPriceValue = 104; + $this->tierPrice->expects($this->atLeastOnce())->method('getPrice')->willReturn($tierPriceValue); + $this->tierPrice->expects($this->atLeastOnce())->method('getPriceType') + ->willReturn($returned['tierPrice_getPriceType']); + $qty = 0; + $this->tierPrice->expects($this->atLeastOnce())->method('getQuantity')->willReturn($qty); + $websiteId = 0; + $invalidWebsiteId = 4; + $this->tierPrice->expects($this->atLeastOnce())->method('getWebsiteId') + ->willReturnOnConsecutiveCalls($websiteId, $websiteId, $websiteId, $invalidWebsiteId, $websiteId); + $this->tierPrice->expects($this->atLeastOnce())->method('getCustomerGroup') + ->willReturn($returned['tierPrice_getCustomerGroup']); + $skuDiff = [$sku]; + $this->invalidSkuProcessor->expects($this->atLeastOnce())->method('retrieveInvalidSkuList') + ->willReturn($skuDiff); + $productId = 3346346; + $productType = \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE; + $idsBySku = [ + $sku => [$productId => $productType] + ]; + $this->productIdLocator->expects($this->atLeastOnce())->method('retrieveProductIdsBySkus') + ->willReturn($idsBySku); + } + + /** + * Test for validateSkus(). + * + * @return void + */ + public function testValidateSkus() + { + $skus = ['SDFS234234']; + $this->invalidSkuProcessor->expects($this->atLeastOnce()) + ->method('filterSkuList') + ->with($skus, []) + ->willReturn($skus); + + $this->assertEquals($skus, $this->tierPriceValidator->validateSkus($skus)); + } + + /** + * Test for retrieveValidationResult(). + * + * @param array $returned + * @dataProvider retrieveValidationResultDataProvider + * @return void + */ + public function testRetrieveValidationResult(array $returned) + { + $sku = 'ASDF234234'; + $prices = [$this->tierPrice]; + $existingPrices = [$this->tierPrice]; + $this->prepareRetrieveValidationResultMethod($sku, $returned); + $website = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->websiteRepository->expects($this->atLeastOnce())->method('getById')->willReturn($website); + $this->prepareCustomerGroupRepositoryMock($returned); + + $this->assertEquals( + $this->validationResult, + $this->tierPriceValidator->retrieveValidationResult($prices, $existingPrices) + ); + } + + /** + * Data provider for retrieveValidationResult() test. + * + * @return array + */ + public function retrieveValidationResultDataProvider() + { + $customerGroupName = 'test_Group'; + $customerGroup = $this->getMockBuilder(\Magento\Customer\Api\Data\GroupInterface::class) + ->setMethods(['getCode', 'getId']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $customerGroup->expects($this->atLeastOnce())->method('getCode')->willReturn($customerGroupName); + $customerGroupId = 23; + $customerGroup->expects($this->atLeastOnce())->method('getId')->willReturn($customerGroupId); + + return [ + [ + [ + 'tierPrice_getCustomerGroup' => $customerGroupName, + 'tierPrice_getPriceType' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT, + 'customerGroupSearchResults_getItems' => [$customerGroup] + ] + ], + [ + [ + 'tierPrice_getCustomerGroup' => $customerGroupName, + 'tierPrice_getPriceType' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_FIXED, + 'customerGroupSearchResults_getItems' => [] + ] + ] + ]; + } + + /** + * Test for retrieveValidationResult() with Exception. + * + * @return void + */ + public function testRetrieveValidationResultWithException() + { + $sku = 'ASDF234234'; + $customerGroupName = 'test_Group'; + $prices = [$this->tierPrice]; + $existingPrices = [$this->tierPrice]; + $returned = [ + 'tierPrice_getPriceType' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT, + 'customerGroupSearchResults_getItems' => [], + 'tierPrice_getCustomerGroup' => $customerGroupName, + ]; + $this->prepareRetrieveValidationResultMethod($sku, $returned); + $exception = new \Magento\Framework\Exception\NoSuchEntityException(); + $this->websiteRepository->expects($this->atLeastOnce())->method('getById')->willThrowException($exception); + $this->prepareCustomerGroupRepositoryMock($returned); + + $this->assertEquals( + $this->validationResult, + $this->tierPriceValidator->retrieveValidationResult($prices, $existingPrices) + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifier/CompositeTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifier/CompositeTest.php index 35aad84447526..7b27d3b07593f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifier/CompositeTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifier/CompositeTest.php @@ -1,6 +1,6 @@ product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['__wakeup', 'getCanShowPrice'], + [], + '', + false + ); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->object = $objectManager->getObject( + \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver::class + ); + } + + public function testSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(true); + + $result = $this->object->isSalable($this->product); + $this->assertTrue($result); + } + + public function testNotSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(false); + + $result = $this->object->isSalable($this->product); + $this->assertFalse($result); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductList/ToolbarTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductList/ToolbarTest.php index 7bafd7a5b5686..a7eb5069d02f0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductList/ToolbarTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductList/ToolbarTest.php @@ -1,6 +1,6 @@ metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->setMethods(['getMetadata']) + ->disableOriginalConstructor()->getMock(); + $this->collectionFactory = $this + ->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor()->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\ProductIdLocator::class, + [ + 'metadataPool' => $this->metadataPool, + 'collectionFactory' => $this->collectionFactory, + ] + ); + } + + /** + * Test retrieve + */ + public function testRetrieveProductIdsBySkus() + { + $skus = ['sku_1', 'sku_2']; + $collection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class) + ->setMethods(['getIterator', 'addFieldToFilter']) + ->disableOriginalConstructor()->getMock(); + $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) + ->setMethods(['getSku', 'getData', 'getTypeId']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $metaDataInterface = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->setMethods(['getLinkField']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); + $collection->expects($this->once())->method('addFieldToFilter') + ->with(\Magento\Catalog\Api\Data\ProductInterface::SKU, ['in' => $skus])->willReturnSelf(); + $collection->expects($this->once())->method('getIterator')->willReturn(new \ArrayIterator([$product])); + $this->metadataPool + ->expects($this->once()) + ->method('getMetadata') + ->with(\Magento\Catalog\Api\Data\ProductInterface::class) + ->willReturn($metaDataInterface); + $metaDataInterface->expects($this->once())->method('getLinkField')->willReturn('entity_id'); + $product->expects($this->once())->method('getSku')->willReturn('sku_1'); + $product->expects($this->once())->method('getData')->with('entity_id')->willReturn(1); + $product->expects($this->once())->method('getTypeId')->willReturn('simple'); + $this->assertEquals( + ['sku_1' => [1 => 'simple']], + $this->model->retrieveProductIdsBySkus($skus) + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/ManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/ManagementTest.php index d5ff3f580b457..4fb25b5927325 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/ManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/ManagementTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/product_options_valid.xml b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/product_options_valid.xml index 093521a0b7a49..5f418c4b177ad 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/product_options_valid.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/product_options_valid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index 5042ac1b745cf..37f16ec4964dc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -1,7 +1,7 @@ expects($this->any())->method('getWebsiteId')->willReturn('1'); $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->mediaGalleryProcessor = $this->getMock( \Magento\Catalog\Model\Product\Gallery\Processor::class, @@ -495,6 +495,7 @@ public function testGetBySkuFromCacheInitializedInGetById() public function testSaveExisting() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); $this->productFactoryMock->expects($this->any()) ->method('create') @@ -514,6 +515,7 @@ public function testSaveExisting() public function testSaveNew() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); $this->resourceModelMock->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); $this->productFactoryMock->expects($this->any()) @@ -538,6 +540,7 @@ public function testSaveNew() */ public function testSaveUnableToSaveException() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); $this->productFactoryMock->expects($this->exactly(2)) ->method('create') @@ -562,6 +565,7 @@ public function testSaveUnableToSaveException() */ public function testSaveException() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); $this->productFactoryMock->expects($this->exactly(2)) ->method('create') @@ -587,6 +591,7 @@ public function testSaveException() */ public function testSaveInvalidProductException() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); $this->productFactoryMock->expects($this->exactly(2)) ->method('create') @@ -610,6 +615,7 @@ public function testSaveInvalidProductException() */ public function testSaveThrowsTemporaryStateExceptionIfDatabaseConnectionErrorOccurred() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->productFactoryMock->expects($this->any()) ->method('create') ->will($this->returnValue($this->productMock)); @@ -709,6 +715,7 @@ public function testGetList() ->method('process') ->with($searchCriteriaMock, $collectionMock); $collectionMock->expects($this->once())->method('load'); + $collectionMock->expects($this->once())->method('addCategoryIds'); $collectionMock->expects($this->once())->method('getItems')->willReturn([$itemsMock]); $collectionMock->expects($this->once())->method('getSize')->willReturn(128); $searchResultsMock = $this->getMock( @@ -796,6 +803,7 @@ public function cacheKeyDataProvider() */ public function testSaveExistingWithOptions(array $newOptions, array $existingOptions, array $expectedData) { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); $this->productFactoryMock->expects($this->any()) ->method('create') @@ -964,6 +972,7 @@ public function saveExistingWithOptionsDataProvider() */ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $expectedData) { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); $this->productFactoryMock->expects($this->any()) ->method('create') @@ -1143,30 +1152,37 @@ protected function setupProductMocksForSave() public function testSaveExistingWithNewMediaGalleryEntries() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $newEntriesData = [ - [ - "label" => "label_text", - 'position' => 10, - 'disabled' => false, - 'types' => ['image', 'small_image'], - 'content' => [ - ImageContentInterface::NAME => 'filename', - ImageContentInterface::TYPE => 'image/jpeg', - ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', - ], - 'media_type' => 'media_type', - ], + 'images' => + [ + [ + 'value_id' => null, + 'label' => "label_text", + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + 'data' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + ], + 'media_type' => 'media_type', + ] + ] ]; $this->setupProductMocksForSave(); //media gallery data - $this->productData['media_gallery_entries'] = $newEntriesData; + $this->productData['media_gallery'] = $newEntriesData; $this->extensibleDataObjectConverterMock ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->initializedProductMock->setData('media_gallery', []); + $this->initializedProductMock->setData('media_gallery', $newEntriesData); $this->initializedProductMock->expects($this->any()) ->method('getMediaAttributes') ->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]); @@ -1222,12 +1238,52 @@ public function testSaveExistingWithNewMediaGalleryEntries() $this->model->save($this->productMock); } + public function websitesProvider() + { + return [ + [[1,2,3]] + ]; + } + + public function testSaveWithDifferentWebsites() + { + $storeMock = $this->getMock(StoreInterface::class); + $this->resourceModelMock->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); + $this->resourceModelMock->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactoryMock->expects($this->any()) + ->method('create') + ->will($this->returnValue($this->productMock)); + $this->initializationHelperMock->expects($this->never())->method('initialize'); + $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->willReturn(true); + $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock)->willReturn(true); + $this->extensibleDataObjectConverterMock + ->expects($this->once()) + ->method('toNestedArray') + ->will($this->returnValue($this->productData)); + $this->storeManagerMock->expects($this->any()) + ->method('getStore') + ->willReturn($storeMock); + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([ + 1 => ['first'], + 2 => ['second'], + 3 => ['third'] + ]); + $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([1,2,3]); + $this->productMock->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]); + + $this->assertEquals($this->productMock, $this->model->save($this->productMock)); + } + public function testSaveExistingWithMediaGalleryEntries() { + $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); //update one entry, delete one entry $newEntries = [ [ - 'id' => 5, + 'value_id' => 5, "label" => "new_label_text", 'file' => 'filename1', 'position' => 10, @@ -1254,7 +1310,7 @@ public function testSaveExistingWithMediaGalleryEntries() $expectedResult = [ [ - 'id' => 5, + 'value_id' => 5, 'value_id' => 5, "label" => "new_label_text", 'file' => 'filename1', @@ -1271,7 +1327,7 @@ public function testSaveExistingWithMediaGalleryEntries() $this->setupProductMocksForSave(); //media gallery data - $this->productData['media_gallery_entries'] = $newEntries; + $this->productData['media_gallery']['images'] = $newEntries; $this->extensibleDataObjectConverterMock ->expects($this->once()) ->method('toNestedArray') diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index dd858fa571ae1..2c44e0a50f983 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -1,6 +1,6 @@ [ - ['catalog_product_1'], + ['cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1]], ['id' => 1, 'name' => 'value', 'category_ids' => [1]], ], 'new product' => $this->getNewProductProviderData(), 'status and category change' => [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_1', 2 => 'catalog_category_product_2'], + [0 => 'cat_p_1', 1 => 'cat_c_p_1', 2 => 'cat_c_p_2'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 2], [ 'id' => 1, @@ -726,18 +726,18 @@ public function getIdentitiesProvider() ], ], 'status change only' => [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_7'], + [0 => 'cat_p_1', 1 => 'cat_c_p_7'], ['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => 1], ['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => 2], ], 'status changed, category unassigned' => $this->getStatusAndCategoryChangesData(), 'no status changes' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], ], 'no stock status changes' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -749,7 +749,7 @@ public function getIdentitiesProvider() ], ], 'no stock status data 1' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -760,7 +760,7 @@ public function getIdentitiesProvider() ], ], 'no stock status data 2' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -780,7 +780,7 @@ public function getIdentitiesProvider() private function getStatusAndCategoryChangesData() { return [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_5'], + [0 => 'cat_p_1', 1 => 'cat_c_p_5'], ['id' => 1, 'name' => 'value', 'category_ids' => [5], 'status' => 2], [ 'id' => 1, @@ -799,7 +799,7 @@ private function getStatusAndCategoryChangesData() private function getNewProductProviderData() { return [ - ['catalog_product_1', 'catalog_category_product_1'], + ['cat_p_1', 'cat_c_p_1'], null, [ 'id' => 1, @@ -818,7 +818,7 @@ private function getNewProductProviderData() private function getStatusStockProviderData($extensionAttributesMock) { return [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_1'], + [0 => 'cat_p_1', 1 => 'cat_c_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -831,20 +831,6 @@ private function getStatusStockProviderData($extensionAttributesMock) ]; } - public function testStatusAfterLoad() - { - $this->resource->expects($this->once())->method('load')->with($this->model, 1, null); - $this->eventManagerMock->expects($this->exactly(4))->method('dispatch'); - $this->model->load(1); - $this->assertEquals( - Status::STATUS_ENABLED, - $this->model->getData(\Magento\Catalog\Model\Product::STATUS) - ); - $this->assertFalse($this->model->hasDataChanges()); - $this->model->setStatus(Status::STATUS_DISABLED); - $this->assertTrue($this->model->hasDataChanges()); - } - /** * Test retrieving price Info */ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypeListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypeListTest.php index 63254a653ab35..6b0bea2c191f3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypeListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypeListTest.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types.xml b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types.xml index 525beaf93c6e9..dc5284d1e5405 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml index 96a8c06c1db2a..724203272620b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/ConfigTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/ConfigTest.php index af0c1625f9cb6..64b8b05b5755d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/ConfigTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/ConfigTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->readerMock = $this->getMock( \Magento\Catalog\Model\ProductTypes\Config\Reader::class, [], @@ -32,19 +43,35 @@ protected function setUp() false ); $this->cacheMock = $this->getMock(\Magento\Framework\Config\CacheInterface::class); + $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\SerializerInterface::class); } /** - * @dataProvider getTypeDataProvider - * * @param array $value * @param mixed $expected + * @dataProvider getTypeDataProvider */ public function testGetType($value, $expected) { - $this->cacheMock->expects($this->any())->method('load')->will($this->returnValue(serialize($value))); - $this->model = new \Magento\Catalog\Model\ProductTypes\Config($this->readerMock, $this->cacheMock, 'cache_id'); - $this->assertEquals($expected, $this->model->getType('global')); + $this->cacheMock->expects($this->any()) + ->method('load') + ->willReturn('serializedData'); + + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with('serializedData') + ->willReturn($value); + + $this->config = $this->objectManager->getObject( + \Magento\Catalog\Model\ProductTypes\Config::class, + [ + 'reader' => $this->readerMock, + 'cache' => $this->cacheMock, + 'cacheId' => 'cache_id', + 'serializer' => $this->serializerMock, + ] + ); + $this->assertEquals($expected, $this->config->getType('global')); } public function getTypeDataProvider() @@ -58,22 +85,43 @@ public function getTypeDataProvider() public function testGetAll() { $expected = ['Expected Data']; - $this->cacheMock->expects( - $this->once() - )->method( - 'load' - )->will( - $this->returnValue(serialize(['types' => $expected])) + $this->cacheMock->expects($this->once()) + ->method('load') + ->willReturn(json_encode('"types":["Expected Data"]]')); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn(['types' => $expected]); + + $this->config = $this->objectManager->getObject( + \Magento\Catalog\Model\ProductTypes\Config::class, + [ + 'reader' => $this->readerMock, + 'cache' => $this->cacheMock, + 'cacheId' => 'cache_id', + 'serializer' => $this->serializerMock, + ] ); - $this->model = new \Magento\Catalog\Model\ProductTypes\Config($this->readerMock, $this->cacheMock, 'cache_id'); - $this->assertEquals($expected, $this->model->getAll()); + $this->assertEquals($expected, $this->config->getAll()); } public function testIsProductSet() { - $this->cacheMock->expects($this->once())->method('load')->will($this->returnValue(serialize([]))); - $this->model = new \Magento\Catalog\Model\ProductTypes\Config($this->readerMock, $this->cacheMock, 'cache_id'); + $this->cacheMock->expects($this->once()) + ->method('load') + ->willReturn(''); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn([]); - $this->assertEquals(false, $this->model->isProductSet('typeId')); + $this->config = $this->objectManager->getObject( + \Magento\Catalog\Model\ProductTypes\Config::class, + [ + 'reader' => $this->readerMock, + 'cache' => $this->cacheMock, + 'cacheId' => 'cache_id', + 'serializer' => $this->serializerMock, + ] + ); + $this->assertEquals(false, $this->config->isProductSet('typeId')); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php index 044bb6cfc9033..e7b7926edc562 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $entityFactory = $this->getMock(\Magento\Framework\Data\Collection\EntityFactory::class, [], [], '', false); $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) ->disableOriginalConstructor() @@ -100,27 +123,39 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) + $this->entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) ->disableOriginalConstructor() ->getMock(); + $this->galleryResourceMock = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Gallery::class + )->disableOriginalConstructor()->getMock(); + + $this->metadataPoolMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\MetadataPool::class + )->disableOriginalConstructor()->getMock(); + + $this->galleryReadHandlerMock = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Gallery\ReadHandler::class + )->disableOriginalConstructor()->getMock(); + $storeManager->expects($this->any())->method('getId')->willReturn(1); $storeManager->expects($this->any())->method('getStore')->willReturnSelf(); $universalFactory->expects($this->exactly(1))->method('create')->willReturnOnConsecutiveCalls( - $entityMock + $this->entityMock ); - $entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); - $entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]); - $entityMock->expects($this->any())->method('getTable')->willReturnArgument(0); + $this->entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); + $this->entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]); + $this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0); $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); - $helper = new ObjectManager($this); - $this->prepareObjectManager([ - [\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class, - $this->getMock(\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class) - ] - ]); - $this->collection = $helper->getObject( + $productLimitationMock = $this->getMock( + \Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class + ); + $productLimitationFactoryMock = $this->getMock(ProductLimitationFactory::class, ['create']); + $productLimitationFactoryMock->method('create') + ->willReturn($productLimitationMock); + $this->collection = $this->objectManager->getObject( \Magento\Catalog\Model\ResourceModel\Product\Collection::class, [ 'entityFactory' => $entityFactory, @@ -142,16 +177,28 @@ protected function setUp() 'customerSession' => $customerSession, 'dateTime' => $dateTime, 'groupManagement' => $groupManagement, - 'connection' => $this->connectionMock + 'connection' => $this->connectionMock, + 'productLimitationFactory' => $productLimitationFactoryMock, + 'metadataPool' => $this->metadataPoolMock, ] ); $this->collection->setConnection($this->connectionMock); + $this->objectManager->setBackwardCompatibleProperty( + $this->collection, + 'mediaGalleryResource', + $this->galleryResourceMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->collection, + 'productGalleryReadHandler', + $this->galleryReadHandlerMock + ); } public function testAddProductCategoriesFilter() { - $condition = ['in' => [1,2]]; - $values = [1,2]; + $condition = ['in' => [1, 2]]; + $values = [1, 2]; $conditionType = 'nin'; $preparedSql = "category_id IN(1,2)"; $tableName = "catalog_category_product"; @@ -174,19 +221,45 @@ public function testAddProductCategoriesFilter() $this->collection->addCategoriesFilter([$conditionType => $values]); } - /** - * @param $map - */ - private function prepareObjectManager($map) + public function testAddMediaGalleryData() { - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('getInstance')->willReturnSelf(); - $objectManagerMock->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($map)); - $reflectionClass = new \ReflectionClass(\Magento\Framework\App\ObjectManager::class); - $reflectionProperty = $reflectionClass->getProperty('_instance'); + $attributeId = 42; + $rowId = 4; + $linkField = 'row_id'; + $mediaGalleriesMock = [[$linkField => $rowId]]; + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getData']) + ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collection->addItem($itemMock); + $reflection = new \ReflectionClass(get_class($this->collection)); + $reflectionProperty = $reflection->getProperty('_isCollectionLoaded'); $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($objectManagerMock); + $reflectionProperty->setValue($this->collection, true); + + $this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock); + $attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->entityMock->expects($this->once())->method('getAttribute')->willReturn($attributeMock); + $itemMock->expects($this->atLeastOnce())->method('getData')->willReturn($rowId); + $selectMock->expects($this->once())->method('where')->with('entity.' . $linkField . ' IN (?)', [$rowId]); + $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadataMock); + $metadataMock->expects($this->once())->method('getLinkField')->willReturn($linkField); + + $this->connectionMock->expects($this->once())->method('fetchAll')->with($selectMock)->willReturn( + [['row_id' => $rowId]] + ); + $this->galleryReadHandlerMock->expects($this->once())->method('addMediaDataToProduct') + ->with($itemMock, $mediaGalleriesMock); + + $this->assertSame($this->collection, $this->collection->addMediaGalleryData()); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php new file mode 100644 index 0000000000000..30060c81883eb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php @@ -0,0 +1,54 @@ +objectManager = new ObjectManager($this); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testInitializeWithWrongProcessorInstance() + { + $processorValid = $this->getMock(BaseSelectProcessorInterface::class); + $processorInvalid = $this->getMock(\stdClass::class); + + $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [ + 'baseSelectProcessors' => [$processorValid, $processorInvalid], + ]); + } + + public function testProcess() + { + $select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); + + $processorFirst = $this->getMock(BaseSelectProcessorInterface::class); + $processorFirst->expects($this->once())->method('process')->with($select)->willReturn($select); + + $processorSecond = $this->getMock(BaseSelectProcessorInterface::class); + $processorSecond->expects($this->once())->method('process')->with($select)->willReturn($select); + + /** @var CompositeBaseSelectProcessor $baseSelectProcessors */ + $baseSelectProcessors = $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [ + 'baseSelectProcessors' => [$processorFirst, $processorSecond], + ]); + $this->assertEquals($select, $baseSelectProcessors->process($select)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/FlatTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/FlatTest.php index a05a627693260..8fed73a4d007a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/FlatTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/FlatTest.php @@ -1,6 +1,6 @@ resource->deleteGalleryValueInStore($valueId, $entityId, $storeId); } + + public function testCountImageUses() + { + $results = [ + [ + 'value_id' => '1', + 'attribute_id' => 90, + 'value' => '/d/o/download_7.jpg', + 'media_type' => 'image', + 'disabled' => '0', + ], + ]; + + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => 'table', + ], + '*' + )->willReturnSelf(); + $this->select->expects($this->at(1))->method('where')->with( + 'value = ?', + 1 + )->willReturnSelf(); + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($results); + $this->assertEquals($this->resource->countImageUses(1), count($results)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php index 3c92cde30012d..bb7250a904f61 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php @@ -1,12 +1,12 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->entityFactoryMock = $this->getMock( \Magento\Framework\Data\Collection\EntityFactory::class, [], @@ -133,14 +134,11 @@ function ($store) { $this->timezoneInterfaceMock = $this->getMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $this->sessionMock = $this->getMock(\Magento\Customer\Model\Session::class, [], [], '', false); $this->dateTimeMock = $this->getMock(\Magento\Framework\Stdlib\DateTime::class); - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->prepareObjectManager([ - [\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class, - $this->getMock(\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class) - ] - ]); + $productLimitationFactoryMock = $this->getMock(ProductLimitationFactory::class, ['create']); + $productLimitationFactoryMock->method('create') + ->willReturn($this->getMock(ProductLimitation::class)); - $this->collection = $this->objectManagerHelper->getObject( + $this->collection = $this->objectManager->getObject( \Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection::class, [ 'entityFactory' => $this->entityFactoryMock, @@ -160,7 +158,8 @@ function ($store) { 'catalogUrl' => $this->urlMock, 'localeDate' => $this->timezoneInterfaceMock, 'customerSession' => $this->sessionMock, - 'dateTime' => $this->dateTimeMock + 'dateTime' => $this->dateTimeMock, + 'productLimitationFactory' => $productLimitationFactoryMock, ] ); } @@ -175,20 +174,4 @@ public function testSetProduct() $this->collection->setProduct($product); $this->assertEquals(33, $this->collection->getStoreId()); } - - /** - * @param $map - */ - public function prepareObjectManager($map) - { - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('getInstance')->willReturnSelf(); - $objectManagerMock->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($map)); - $reflectionClass = new \ReflectionClass(\Magento\Framework\App\ObjectManager::class); - $reflectionProperty = $reflectionClass->getProperty('_instance'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($objectManagerMock); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/LinkTest.php index 50f14246ed9ee..b9fb5a717c8c7 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/LinkTest.php @@ -1,6 +1,6 @@ objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->entityFactoryMock = $this->getMock( \Magento\Framework\Data\Collection\EntityFactory::class, ['create'], [], '', false ); @@ -147,11 +150,6 @@ protected function setUp() $this->metadataPoolMock->expects($this->any())->method('getMetadata')->willReturn($metadata); $this->selectMock->expects($this->exactly(2))->method('join'); - $this->prepareObjectManager([ - [\Magento\Framework\EntityManager\MetadataPool::class, $this->metadataPoolMock], - [\Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::class, $this->joinProcessor] - ]); - $this->collection = new Collection( $this->entityFactoryMock, $this->loggerMock, @@ -160,7 +158,13 @@ protected function setUp() $this->optionsFactoryMock, $this->storeManagerMock, null, - $this->resourceMock + $this->resourceMock, + $this->metadataPoolMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->collection, + 'joinProcessor', + $this->joinProcessor ); } @@ -168,20 +172,4 @@ public function testReset() { $this->collection->reset(); } - - /** - * @param $map - */ - private function prepareObjectManager($map) - { - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('getInstance')->willReturnSelf(); - $objectManagerMock->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($map)); - $reflectionClass = new \ReflectionClass(\Magento\Framework\App\ObjectManager::class); - $reflectionProperty = $reflectionClass->getProperty('_instance'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($objectManagerMock); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php new file mode 100644 index 0000000000000..0a828a9106dff --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php @@ -0,0 +1,129 @@ +eavConfig = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $this->metadataPool = $this->getMockBuilder(MetadataPool::class)->disableOriginalConstructor()->getMock(); + $this->storeResolver = $this->getMockBuilder(StoreResolverInterface::class)->getMock(); + $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); + + $this->statusBaseSelectProcessor = (new ObjectManager($this))->getObject(StatusBaseSelectProcessor::class, [ + 'eavConfig' => $this->eavConfig, + 'metadataPool' => $this->metadataPool, + 'storeResolver' => $this->storeResolver, + ]); + } + + public function testProcess() + { + $linkField = 'link_field'; + $backendTable = 'backend_table'; + $attributeId = 2; + $currentStoreId = 1; + + $metadata = $this->getMock(EntityMetadataInterface::class); + $metadata->expects($this->once()) + ->method('getLinkField') + ->willReturn($linkField); + $this->metadataPool->expects($this->once()) + ->method('getMetadata') + ->with(ProductInterface::class) + ->willReturn($metadata); + + /** @var AttributeInterface|\PHPUnit_Framework_MockObject_MockObject $statusAttribute */ + $statusAttribute = $this->getMockBuilder(AttributeInterface::class) + ->setMethods(['getBackendTable', 'getAttributeId']) + ->getMock(); + $statusAttribute->expects($this->atLeastOnce()) + ->method('getBackendTable') + ->willReturn($backendTable); + $statusAttribute->expects($this->atLeastOnce()) + ->method('getAttributeId') + ->willReturn($attributeId); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(Product::ENTITY, ProductInterface::STATUS) + ->willReturn($statusAttribute); + + $this->storeResolver->expects($this->once()) + ->method('getCurrentStoreId') + ->willReturn($currentStoreId); + + $this->select->expects($this->at(0)) + ->method('joinLeft') + ->with( + ['status_global_attr' => $backendTable], + "status_global_attr.{$linkField} = " + . BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . " AND status_global_attr.attribute_id = {$attributeId}" + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + ) + ->willReturnSelf(); + $this->select->expects($this->at(1)) + ->method('joinLeft') + ->with( + ['status_attr' => $backendTable], + "status_attr.{$linkField} = " . BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . ".{$linkField}" + . " AND status_attr.attribute_id = {$attributeId}" + . " AND status_attr.store_id = {$currentStoreId}", + [] + ) + ->willReturnSelf(); + $this->select->expects($this->at(2)) + ->method('where') + ->with('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_ENABLED) + ->willReturnSelf(); + + $this->assertEquals($this->select, $this->statusBaseSelectProcessor->process($this->select)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Website/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Website/LinkTest.php index bedeba40eefcf..a01fbc4d2e386 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Website/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Website/LinkTest.php @@ -1,6 +1,6 @@ mediaConfig = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); + $this->mediaConfig->expects($this->any())->method('getBaseMediaPath')->willReturn('catalog/product'); + $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); + $this->mediaDirectory->expects($this->once())->method('create')->with('catalog/product'); + $this->filesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($this->mediaDirectory); + $this->model = new Context( + $this->mediaConfig, + $this->filesystem + ); + } + + public function testGetPath() + { + $path = '/var/www/html/magento2ce/pub/media/catalog/product'; + $this->mediaDirectory->expects($this->once()) + ->method('getAbsolutePath') + ->with('catalog/product') + ->willReturn($path); + + $this->assertEquals($path, $this->model->getPath()); + } + + public function testGetUrl() + { + $baseUrl = 'http://localhost/pub/media/catalog/product'; + $this->mediaConfig->expects($this->once())->method('getBaseMediaUrl')->willReturn($baseUrl); + + $this->assertEquals($baseUrl, $this->model->getBaseUrl()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php new file mode 100644 index 0000000000000..6c456a02c82b9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php @@ -0,0 +1,172 @@ +mediaConfig = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); + $this->encryptor = $this->getMockBuilder(EncryptorInterface::class)->getMockForAbstractClass(); + $this->imageContext = $this->getMockBuilder(ContextInterface::class)->getMockForAbstractClass(); + $this->model = new Image( + $this->mediaConfig, + $this->imageContext, + $this->encryptor, + '/somefile.png' + ); + } + + public function testModuleAndContentAndContentType() + { + $contentType = 'image'; + $this->assertEquals($contentType, $this->model->getContentType()); + $this->assertEquals($contentType, $this->model->getSourceContentType()); + $this->assertNull($this->model->getContent()); + $this->assertEquals('cache', $this->model->getModule()); + } + + public function testGetFilePath() + { + $this->assertEquals('/somefile.png', $this->model->getFilePath()); + } + + public function testGetSoureFile() + { + $this->mediaConfig->expects($this->once())->method('getBaseMediaPath')->willReturn('catalog/product'); + $this->assertEquals('catalog/product/somefile.png', $this->model->getSourceFile()); + } + + public function testGetContext() + { + $this->assertInstanceOf(ContextInterface::class, $this->model->getContext()); + } + + /** + * @param string $filePath + * @param array $miscParams + * @dataProvider getPathDataProvider + */ + public function testGetPath($filePath, $miscParams) + { + $imageModel = new Image( + $this->mediaConfig, + $this->imageContext, + $this->encryptor, + $filePath, + $miscParams + ); + $absolutePath = '/var/www/html/magento2ce/pub/media/catalog/product'; + $hashPath = md5(implode('_', $miscParams)); + $this->imageContext->expects($this->once())->method('getPath')->willReturn($absolutePath); + $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); + $this->assertEquals( + $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, + $imageModel->getPath() + ); + } + + /** + * @param string $filePath + * @param array $miscParams + * @dataProvider getPathDataProvider + */ + public function testGetNotUnixPath($filePath, $miscParams) + { + $imageModel = new Image( + $this->mediaConfig, + $this->imageContext, + $this->encryptor, + $filePath, + $miscParams + ); + $absolutePath = 'C:\www\magento2ce\pub\media\catalog\product'; + $hashPath = md5(implode('_', $miscParams)); + $this->imageContext->expects($this->once())->method('getPath')->willReturn($absolutePath); + $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); + $this->assertEquals( + $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, + $imageModel->getPath() + ); + } + + /** + * @param string $filePath + * @param array $miscParams + * @dataProvider getPathDataProvider + */ + public function testGetUrl($filePath, $miscParams) + { + $imageModel = new Image( + $this->mediaConfig, + $this->imageContext, + $this->encryptor, + $filePath, + $miscParams + ); + $absolutePath = 'http://localhost/pub/media/catalog/product'; + $hashPath = md5(implode('_', $miscParams)); + $this->imageContext->expects($this->once())->method('getBaseUrl')->willReturn($absolutePath); + $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); + $this->assertEquals( + $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, + $imageModel->getUrl() + ); + } + + public function getPathDataProvider() + { + return [ + [ + '/some_file.png', + [], //default value for miscParams + ], + [ + '/some_file_2.png', + [ + 'image_type' => 'thumbnail', + 'image_height' => 75, + 'image_width' => 75, + 'keep_aspect_ratio' => 'proportional', + 'keep_frame' => 'frame', + 'keep_transparency' => 'transparency', + 'constrain_only' => 'doconstrainonly', + 'background' => 'ffffff', + 'angle' => null, + 'quality' => 80, + ], + ] + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/PlaceholderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/PlaceholderTest.php new file mode 100644 index 0000000000000..ec45cd6f72127 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/PlaceholderTest.php @@ -0,0 +1,163 @@ +scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)->getMockForAbstractClass(); + $this->imageContext = $this->getMockBuilder(ContextInterface::class)->getMockForAbstractClass(); + $this->repository = $this->getMockBuilder(Repository::class)->disableOriginalConstructor()->getMock(); + $this->model = new Placeholder( + $this->imageContext, + $this->scopeConfig, + $this->repository, + 'thumbnail' + ); + } + + public function testModuleAndContentAndContentType() + { + $contentType = 'image'; + $this->assertEquals($contentType, $this->model->getContentType()); + $this->assertEquals($contentType, $this->model->getSourceContentType()); + $this->assertNull($this->model->getContent()); + $this->assertEquals('placeholder', $this->model->getModule()); + } + + public function testGetFilePath() + { + $this->assertNull($this->model->getFilePath()); + $this->scopeConfig->expects($this->once())->method('getValue')->willReturn('default/thumbnail.jpg'); + $this->assertEquals('default/thumbnail.jpg', $this->model->getFilePath()); + } + + public function testGetContext() + { + $this->assertInstanceOf(ContextInterface::class, $this->model->getContext()); + } + + /** + * @param string $imageType + * @param string $placeholderPath + * @dataProvider getPathDataProvider + */ + public function testGetPathAndGetSourceFile($imageType, $placeholderPath) + { + $imageModel = new Placeholder( + $this->imageContext, + $this->scopeConfig, + $this->repository, + $imageType + ); + $absolutePath = '/var/www/html/magento2ce/pub/media/catalog/product'; + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->with( + "catalog/placeholder/{$imageType}_placeholder", + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + null + )->willReturn($placeholderPath); + + if ($placeholderPath == null) { + $this->imageContext->expects($this->never())->method('getPath'); + $assetMock = $this->getMockBuilder(\Magento\Framework\View\Asset\MergeableInterface::class) + ->getMockForAbstractClass(); + $expectedResult = 'path/to_default/placeholder/by_type'; + $assetMock->expects($this->any())->method('getSourceFile')->willReturn($expectedResult); + $this->repository->expects($this->any())->method('createAsset')->willReturn($assetMock); + } else { + $this->imageContext->expects($this->any())->method('getPath')->willReturn($absolutePath); + $expectedResult = $absolutePath + . DIRECTORY_SEPARATOR . $imageModel->getModule() + . DIRECTORY_SEPARATOR . $placeholderPath; + } + + $this->assertEquals($expectedResult, $imageModel->getPath()); + $this->assertEquals($expectedResult, $imageModel->getSourceFile()); + } + + /** + * @param string $imageType + * @param string $placeholderPath + * @dataProvider getPathDataProvider + */ + public function testGetUrl($imageType, $placeholderPath) + { + $imageModel = new Placeholder( + $this->imageContext, + $this->scopeConfig, + $this->repository, + $imageType + ); + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->with( + "catalog/placeholder/{$imageType}_placeholder", + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + null + )->willReturn($placeholderPath); + + if ($placeholderPath == null) { + $this->imageContext->expects($this->never())->method('getBaseUrl'); + $expectedResult = 'http://localhost/pub/media/catalog/product/to_default/placeholder/by_type'; + $this->repository->expects($this->any())->method('getUrl')->willReturn($expectedResult); + } else { + $baseUrl = 'http://localhost/pub/media/catalog/product'; + $this->imageContext->expects($this->any())->method('getBaseUrl')->willReturn($baseUrl); + $expectedResult = $baseUrl + . DIRECTORY_SEPARATOR . $imageModel->getModule() + . DIRECTORY_SEPARATOR . $placeholderPath; + } + + $this->assertEquals($expectedResult, $imageModel->getUrl()); + } + + public function getPathDataProvider() + { + return [ + [ + 'thumbnail', + 'default/thumbnail.jpg', + ], + [ + 'non_exist', + null, + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php index 49d865253ced4..d173b9dc05a32 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/Test/Unit/Observer/MenuCategoryDataTest.php b/app/code/Magento/Catalog/Test/Unit/Observer/MenuCategoryDataTest.php index fde1c74bc5ae5..a935861a263e1 100644 --- a/app/code/Magento/Catalog/Test/Unit/Observer/MenuCategoryDataTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Observer/MenuCategoryDataTest.php @@ -1,6 +1,6 @@ cache = $this->getMock(\Magento\Framework\App\CacheInterface::class); $this->cacheState = $this->getMock(\Magento\Framework\App\Cache\StateInterface::class); + $this->serializer = $this->getMock(SerializerInterface::class); $this->subject = $this->getMock(\Magento\Catalog\Model\ResourceModel\Config::class, [], [], '', false); } @@ -47,12 +50,17 @@ public function testGetAttributesUsedInListingFromCache() $entityTypeId = 'type'; $storeId = 'store'; $attributes = ['attributes']; + $serializedAttributes = '["attributes"]'; $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(serialize($attributes)); + $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn($serializedAttributes); + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with($serializedAttributes) + ->willReturn($attributes); $this->assertEquals( $attributes, @@ -68,14 +76,21 @@ public function testGetAttributesUsedInListingWithCacheSave() $entityTypeId = 'type'; $storeId = 'store'; $attributes = ['attributes']; + $serializedAttributes = '["attributes"]'; $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(false); + $this->serializer->expects($this->never()) + ->method('unserialize'); + $this->serializer->expects($this->once()) + ->method('serialize') + ->with($attributes) + ->willReturn($serializedAttributes); $this->cache->expects($this->any())->method('save')->with( - serialize($attributes), + $serializedAttributes, $cacheId, [ \Magento\Eav\Model\Cache\Type::CACHE_TAG, @@ -110,11 +125,16 @@ public function testGetAttributesUsedForSortByFromCache() $entityTypeId = 'type'; $storeId = 'store'; $attributes = ['attributes']; + $serializedAttributes = '["attributes"]'; $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(serialize($attributes)); + $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn($serializedAttributes); + $this->serializer->expects($this->once()) + ->method('unserialize') + ->with($serializedAttributes) + ->willReturn($attributes); $this->assertEquals( $attributes, @@ -130,13 +150,20 @@ public function testGetAttributesUsedForSortByWithCacheSave() $entityTypeId = 'type'; $storeId = 'store'; $attributes = ['attributes']; + $serializedAttributes = '["attributes"]'; $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(false); + $this->serializer->expects($this->never()) + ->method('unserialize'); + $this->serializer->expects($this->once()) + ->method('serialize') + ->with($attributes) + ->willReturn($serializedAttributes); $this->cache->expects($this->any())->method('save')->with( - serialize($attributes), + $serializedAttributes, $cacheId, [ \Magento\Eav\Model\Cache\Type::CACHE_TAG, @@ -165,7 +192,8 @@ protected function getConfig($cacheEnabledFlag) \Magento\Catalog\Plugin\Model\ResourceModel\Config::class, [ 'cache' => $this->cache, - 'cacheState' => $this->cacheState + 'cacheState' => $this->cacheState, + 'serializer' => $this->serializer, ] ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php index 3cb1749dc4add..11ff04d0aa0fb 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php @@ -1,6 +1,6 @@ price = $this->getMock(TierPrice::class, [], [], '', false); + $this->priceInfo = $this->getMockForAbstractClass(PriceInfoInterface::class); + $this->saleable = $this->getMockForAbstractClass(SaleableInterface::class); + + $this->objectManager = new ObjectManager($this); + + $this->calculator = $this->getMockForAbstractClass(CalculatorInterface::class); + $this->object = $this->objectManager->getObject( + MinimalTierPriceCalculator::class, + ['calculator' => $this->calculator] + ); + } + + private function getValueTierPricesExistShouldReturnMinTierPrice() + { + $minPrice = 5; + $notMinPrice = 10; + + $minAmount = $this->getMockForAbstractClass(AmountInterface::class); + $minAmount->expects($this->once())->method('getValue')->willReturn($minPrice); + + $notMinAmount = $this->getMockForAbstractClass(AmountInterface::class); + $notMinAmount->expects($this->once())->method('getValue')->willReturn($notMinPrice); + + $tierPriceList = [ + [ + 'price' => $minAmount + ], + [ + 'price' => $notMinAmount + ] + ]; + + $this->price->expects($this->once())->method('getTierPriceList')->willReturn($tierPriceList); + + $this->priceInfo->expects($this->once())->method('getPrice')->with(TierPrice::PRICE_CODE) + ->willReturn($this->price); + + $this->saleable->expects($this->once())->method('getPriceInfo')->willReturn($this->priceInfo); + return $minPrice; + } + + public function testGetValueTierPricesExistShouldReturnMinTierPrice() + { + $minPrice = $this->getValueTierPricesExistShouldReturnMinTierPrice(); + $this->assertEquals($minPrice, $this->object->getValue($this->saleable)); + } + + public function testGetGetAmountMinTierPriceExistShouldReturnAmountObject() + { + $minPrice = $this->getValueTierPricesExistShouldReturnMinTierPrice(); + + $amount = $this->getMockForAbstractClass(AmountInterface::class); + + $this->calculator->expects($this->once()) + ->method('getAmount') + ->with($minPrice, $this->saleable) + ->willReturn($amount); + + $this->assertSame($amount, $this->object->getAmount($this->saleable)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/RegularPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/RegularPriceTest.php index 09988088c1632..54259101469bd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/RegularPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/RegularPriceTest.php @@ -1,6 +1,6 @@ product = $this->getMock( \Magento\Catalog\Model\Product::class, - ['getPriceInfo', '__wakeup', 'getCanShowPrice'], + ['getPriceInfo', '__wakeup', 'getCanShowPrice', 'isSalable'], [], '', false @@ -78,9 +94,7 @@ protected function setUp() $this->priceBox = $this->getMock(\Magento\Framework\Pricing\Render\PriceBox::class, [], [], '', false); $this->logger = $this->getMock(\Psr\Log\LoggerInterface::class); - $this->layout->expects($this->any()) - ->method('getBlock') - ->will($this->returnValue($this->priceBox)); + $this->layout->expects($this->any())->method('getBlock')->willReturn($this->priceBox); $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) ->getMockForAbstractClass(); @@ -93,12 +107,9 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) - ->getMockForAbstractClass(); - - $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->getMockForAbstractClass(); + $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class)->getMockForAbstractClass(); + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass(); $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) ->setMethods(['getStore', 'getCode']) ->getMockForAbstractClass(); @@ -144,6 +155,11 @@ protected function setUp() ->will($this->returnValue(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->salableResolverMock = $this->getMockBuilder(SalableResolverInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->minimalPriceCalculator = $this->getMockForAbstractClass(MinimalPriceCalculatorInterface::class); $this->object = $objectManager->getObject( \Magento\Catalog\Pricing\Render\FinalPriceBox::class, [ @@ -151,7 +167,9 @@ protected function setUp() 'saleableItem' => $this->product, 'rendererPool' => $this->rendererPool, 'price' => $this->price, - 'data' => ['zone' => 'test_zone', 'list_category_page' => true] + 'data' => ['zone' => 'test_zone', 'list_category_page' => true], + 'salableResolver' => $this->salableResolverMock, + 'minimalPriceCalculator' => $this->minimalPriceCalculator ] ); } @@ -169,6 +187,8 @@ public function testRenderMsrpDisabled() ->with($this->equalTo($this->product)) ->will($this->returnValue(false)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -177,6 +197,18 @@ public function testRenderMsrpDisabled() $this->assertRegExp('/[final_price]/', $result); } + public function testNotSalableItem() + { + $this->salableResolverMock + ->expects($this->once()) + ->method('isSalable') + ->with($this->product) + ->willReturn(false); + $result = $this->object->toHtml(); + + $this->assertEmpty($result); + } + public function testRenderMsrpEnabled() { $priceType = $this->getMock(\Magento\Msrp\Pricing\Price\MsrpPrice::class, [], [], '', false); @@ -211,6 +243,8 @@ public function testRenderMsrpEnabled() ->with('msrp_price', $this->product, $arguments) ->will($this->returnValue($priceBoxRender)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -230,6 +264,8 @@ public function testRenderMsrpNotRegisteredException() ->with($this->equalTo('msrp_price')) ->will($this->throwException(new \InvalidArgumentException())); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -240,11 +276,17 @@ public function testRenderMsrpNotRegisteredException() public function testRenderAmountMinimal() { - $priceType = $this->getMock(\Magento\Catalog\Pricing\Price\FinalPrice::class, [], [], '', false); - $amount = $this->getMockForAbstractClass(\Magento\Framework\Pricing\Amount\AmountInterface::class); $priceId = 'price_id'; $html = 'html'; + $this->object->setData('price_id', $priceId); + $this->product->expects($this->never())->method('getId'); + + $amount = $this->getMockForAbstractClass(AmountInterface::class); + + $this->minimalPriceCalculator->expects($this->once())->method('getAmount') + ->with($this->product) + ->willReturn($amount); $arguments = [ 'zone' => 'test_zone', @@ -255,24 +297,15 @@ public function testRenderAmountMinimal() 'skip_adjustments' => true, ]; - $amountRender = $this->getMock(\Magento\Framework\Pricing\Render\Amount::class, ['toHtml'], [], '', false); + $amountRender = $this->getMock(Amount::class, ['toHtml'], [], '', false); $amountRender->expects($this->once()) ->method('toHtml') - ->will($this->returnValue($html)); - - $this->priceInfo->expects($this->once()) - ->method('getPrice') - ->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) - ->will($this->returnValue($priceType)); - - $priceType->expects($this->once()) - ->method('getMinimalPrice') - ->will($this->returnValue($amount)); + ->willReturn($html); $this->rendererPool->expects($this->once()) ->method('createAmountRender') ->with($amount, $this->product, $this->price, $arguments) - ->will($this->returnValue($amountRender)); + ->willReturn($amountRender); $this->assertEquals($html, $this->object->renderAmountMinimal()); } @@ -327,36 +360,29 @@ public function hasSpecialPriceProvider() public function testShowMinimalPrice() { - $finalPrice = 10.0; $minimalPrice = 5.0; - $displayMininmalPrice = 2.0; - - $this->object->setDisplayMinimalPrice($displayMininmalPrice); - - $finalPriceType = $this->getMock(\Magento\Catalog\Pricing\Price\FinalPrice::class, [], [], '', false); + $finalPrice = 10.0; + $displayMininmalPrice = true; - $finalPriceAmount = $this->getMockForAbstractClass(\Magento\Framework\Pricing\Amount\AmountInterface::class); - $minimalPriceAmount = $this->getMockForAbstractClass(\Magento\Framework\Pricing\Amount\AmountInterface::class); + $this->minimalPriceCalculator->expects($this->once())->method('getValue')->with($this->product) + ->willReturn($minimalPrice); + $finalPriceAmount = $this->getMockForAbstractClass(AmountInterface::class); $finalPriceAmount->expects($this->once()) ->method('getValue') ->will($this->returnValue($finalPrice)); - $minimalPriceAmount->expects($this->once()) - ->method('getValue') - ->will($this->returnValue($minimalPrice)); - $finalPriceType->expects($this->at(0)) + $finalPriceType = $this->getMock(FinalPrice::class, [], [], '', false); + $finalPriceType->expects($this->once()) ->method('getAmount') ->will($this->returnValue($finalPriceAmount)); - $finalPriceType->expects($this->at(1)) - ->method('getMinimalPrice') - ->will($this->returnValue($minimalPriceAmount)); $this->priceInfo->expects($this->once()) ->method('getPrice') - ->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) - ->will($this->returnValue($finalPriceType)); + ->with(FinalPrice::PRICE_CODE) + ->willReturn($finalPriceType); + $this->object->setDisplayMinimalPrice($displayMininmalPrice); $this->assertTrue($this->object->showMinimalPrice()); } diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/PriceBoxTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/PriceBoxTest.php index 1e8b930a0c491..1b7cc7926da62 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/PriceBoxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/PriceBoxTest.php @@ -1,6 +1,6 @@ [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::DATA_SOURCE_DEFAULT => [ 'title' => 'original' ] ] ]; $options = [ - $this->getProductOptionMock(['title' => 'option1']), + $this->getProductOptionMock(['title' => 'option1', 'store_title' => 'Option Store Title']), $this->getProductOptionMock( - ['title' => 'option2'], + ['title' => 'option2', 'store_title' => null], [ - $this->getProductOptionMock(['title' => 'value1']), - $this->getProductOptionMock(['title' => 'value2']) + $this->getProductOptionMock(['title' => 'value1', 'store_title' => 'Option Value Store Title']), + $this->getProductOptionMock(['title' => 'value2', 'store_title' => null]) ] ) ]; $resultData = [ $productId => [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ - 'title' => 'original', - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::FIELD_ENABLE => 1, - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::GRID_OPTIONS_NAME => [ - ['title' => 'option1'], + CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::FIELD_TITLE_NAME => 'original', + CustomOptions::FIELD_ENABLE => 1, + CustomOptions::GRID_OPTIONS_NAME => [ [ - 'title' => 'option2', + CustomOptions::FIELD_TITLE_NAME => 'option1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'option2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true, CustomOptions::GRID_TYPE_SELECT_NAME => [ - ['title' => 'value1'], - ['title' => 'value2'] + [ + CustomOptions::FIELD_TITLE_NAME => 'value1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Value Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'value2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true + ] ] ] ] @@ -154,13 +166,13 @@ public function testModifyMeta() */ protected function getProductOptionMock(array $data, array $values = []) { + /** @var ProductOption|\PHPUnit_Framework_MockObject_MockObject $productOptionMock */ $productOptionMock = $this->getMockBuilder(ProductOption::class) ->disableOriginalConstructor() + ->setMethods(['getValues']) ->getMock(); - $productOptionMock->expects($this->any()) - ->method('getData') - ->willReturn($data); + $productOptionMock->setData($data); $productOptionMock->expects($this->any()) ->method('getValues') ->willReturn($values); diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index 2deb4800bd023..c92424fbebbf4 100755 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -1,6 +1,6 @@ assertSame($this->getSampleData(), $this->getModel()->modifyData($this->getSampleData())); + $this->productMock->expects($this->once())->method('getId')->willReturn(2051); + $actualResult = $this->getModel()->modifyData($this->getSampleData()); + $this->assertSame("", $actualResult[2051]['product']['media_gallery']['images'][0]['label']); } public function testModifyMeta() @@ -40,4 +42,24 @@ public function testModifyMeta() $this->assertSame([], $this->getModel()->modifyMeta($meta)); } + + /** + * {@inheritdoc} + */ + protected function getSampleData() + { + return [ + 2051 => [ + 'product' => [ + 'media_gallery' => [ + 'images' => [ + [ + 'label' => null + ] + ] + ] + ] + ] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/RelatedTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/RelatedTest.php index b0b2365c4f151..8bfcc0edd196e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/RelatedTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/RelatedTest.php @@ -1,6 +1,6 @@ productPriceOptions = $this->getMock(ProductPriceOptionsInterface::class); + $this->arrayManager = $this->getMock(ArrayManager::class, [], [], '', false); + + $this->tierPrice = (new ObjectManager($this))->getObject(TierPrice::class, [ + 'productPriceOptions' => $this->productPriceOptions, + 'arrayManager' => $this->arrayManager, + ]); + } + + /** + * Test modifyData. + */ + public function testModifyData() + { + $data = [1, 2]; + $this->assertEquals($data, $this->tierPrice->modifyData($data)); + } + + /** + * Test modifyMeta. + */ + public function testModifyMeta() + { + $meta = [1, 2]; + $tierPricePath = 'tier_price'; + $priceWrapperPath = 'tier_price/some-wrapper'; + $pricePath = $priceWrapperPath . '/price'; + $priceMeta = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'visible' => true, + 'validation' => ['validate-number' => true], + ], + ], + ], + ]; + + $this->productPriceOptions->expects($this->once())->method('toOptionArray')->willReturn([ + [ + 'value' => ProductPriceOptionsInterface::VALUE_FIXED, + 'label' => 'label1', + ], + ]); + + $this->productPriceOptions->expects($this->once())->method('toOptionArray')->willReturn([ + [ + 'value' => ProductPriceOptionsInterface::VALUE_FIXED, + 'label' => 'label1', + ], + ]); + + $this->arrayManager + ->expects($this->exactly(2)) + ->method('findPath') + ->willReturnMap([ + [ + ProductAttributeInterface::CODE_TIER_PRICE, + $meta, + null, + 'children', + ArrayManager::DEFAULT_PATH_DELIMITER, + $tierPricePath + ], + [ + ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE, + $meta, + $tierPricePath, + null, + ArrayManager::DEFAULT_PATH_DELIMITER, + $pricePath + ], + ]); + $this->arrayManager + ->expects($this->once()) + ->method('get') + ->with($pricePath, $meta) + ->willReturn($priceMeta); + $this->arrayManager + ->expects($this->once()) + ->method('remove') + ->with($pricePath, $meta) + ->willReturn($meta); + $this->arrayManager + ->expects($this->once()) + ->method('slicePath') + ->with($pricePath, 0, -1) + ->willReturn($priceWrapperPath); + $this->arrayManager + ->expects($this->once()) + ->method('merge') + ->with($priceWrapperPath, $meta, $this->isType('array')) + ->willReturnArgument(2); + + $modifiedMeta = $this->tierPrice->modifyMeta($meta); + $children = $modifiedMeta['price_value']['children']; + + $this->assertNotEmpty($children[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_VALUE_TYPE]); + $this->assertNotEmpty($children[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE]); + $this->assertEquals($priceMeta, $children[ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE]); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php index deaadb0f12f95..a6b2b82fe6bcc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php @@ -1,6 +1,6 @@ [ 'config' => [ 'componentType' => 'dynamicRows', + 'component' => 'Magento_Catalog/js/components/dynamic-rows-tier-price', 'label' => __('Customer Group Price'), 'renderDefaultRecord' => false, 'recordTemplate' => 'record', @@ -493,7 +494,6 @@ private function getTierPriceStructure($tierPricePath) 'data' => [ 'config' => [ 'componentType' => Field::NAME, - 'component' => 'Magento_Catalog/js/form/element/price-input', 'formElement' => Input::NAME, 'dataType' => Price::NAME, 'label' => __('Price'), diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php index 9ca34ff764dcd..d5aab5340f771 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php @@ -1,6 +1,6 @@ locator = $locator; $this->categoryCollectionFactory = $categoryCollectionFactory; $this->dbHelper = $dbHelper; $this->urlBuilder = $urlBuilder; $this->arrayManager = $arrayManager; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); } /** @@ -286,7 +295,7 @@ protected function getCategoriesTree($filter = null) { $categoryTree = $this->getCacheManager()->load(self::CATEGORY_TREE_ID . '_' . $filter); if ($categoryTree) { - return unserialize($categoryTree); + return $this->serializer->unserialize($categoryTree); } $storeId = $this->locator->getStore()->getId(); @@ -340,7 +349,7 @@ protected function getCategoriesTree($filter = null) } $this->getCacheManager()->save( - serialize($categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']), + $this->serializer->serialize($categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']), self::CATEGORY_TREE_ID . '_' . $filter, [ \Magento\Catalog\Model\Category::CACHE_TAG, diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 5e6c88a1ba93d..9041dcc0380b6 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -1,6 +1,6 @@ $option) { - $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $option->getData()); + $optionData = $option->getData(); + $optionData[static::FIELD_IS_USE_DEFAULT] = !$option->getData(static::FIELD_STORE_TITLE_NAME); + $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $optionData); $values = $option->getValues() ?: []; + foreach ($values as $value) { + $value->setData(static::FIELD_IS_USE_DEFAULT, !$value->getData(static::FIELD_STORE_TITLE_NAME)); + } /** @var \Magento\Catalog\Model\Product\Option $value */ foreach ($values as $value) { $options[$index][static::GRID_TYPE_SELECT_NAME][] = $this->formatPriceByPath( @@ -529,7 +536,8 @@ protected function getCommonContainerConfig($sortOrder) 'component' => 'Magento_Catalog/component/static-type-input', 'valueUpdate' => 'input', 'imports' => [ - 'optionId' => '${ $.provider }:${ $.parentScope }.option_id' + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ] ], ], @@ -604,6 +612,7 @@ protected function getSelectTypeGridConfig($sortOrder) 'imports' => [ 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', 'optionTypeId' => '${ $.provider }:${ $.parentScope }.option_type_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ], 'service' => [ 'template' => 'Magento_Catalog/form/element/helper/custom-option-type-service', @@ -1109,7 +1118,7 @@ private function getLocaleCurrency() } return $this->localeCurrency; } - + /** * Format price according to the locale of the currency * diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 1419c17e11b29..687f6349146c2 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -1,6 +1,6 @@ false, - 'add_widgets' => false + 'add_widgets' => false, + 'add_directives' => true, + 'use_container' => true, + 'container_class' => 'hor-scroll', ]; return $meta; diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php index 0fbba264f7e93..8c4dac63e21eb 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php @@ -1,6 +1,6 @@ $this->locator->getStore()->getConfig('catalog/fields_masks/' . $listener), 'component' => 'Magento_Catalog/js/components/import-handler', - 'imports' => [ - 'handleNameChanges' => '${$.provider}:data.product.name', - 'handleDescriptionChanges' => '${$.provider}:data.product.description', - 'handleSkuChanges' => '${$.provider}:data.product.sku', - 'handleColorChanges' => '${$.provider}:data.product.color', - 'handleCountryChanges' => '${$.provider}:data.product.country_of_manufacture', - 'handleGenderChanges' => '${$.provider}:data.product.gender', - 'handleMaterialChanges' => '${$.provider}:data.product.material', - 'handleShortDescriptionChanges' => '${$.provider}:data.product.short_description', - 'handleSizeChanges' => '${$.provider}:data.product.size' - ], 'allowImport' => !$this->locator->getProduct()->getId(), ]; diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Images.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Images.php index 810a06df4a42f..e7b3e86083a8e 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Images.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Images.php @@ -1,6 +1,6 @@ locator->getProduct(); + $modelId = $product->getId(); + if ( + isset($data[$modelId][self::DATA_SOURCE_DEFAULT]['media_gallery']) + && !empty($data[$modelId][self::DATA_SOURCE_DEFAULT]['media_gallery']) + && !empty($data[$modelId][self::DATA_SOURCE_DEFAULT]['media_gallery']['images']) + ) { + foreach ($data[$modelId][self::DATA_SOURCE_DEFAULT]['media_gallery']['images'] as $index => $image) { + if (!isset($image['label'])) { + $data[$modelId][self::DATA_SOURCE_DEFAULT]['media_gallery']['images'][$index]['label'] = ""; + } + } + }; + return $data; } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php index 9c488cbebe11a..a9f14444a6b09 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php @@ -1,6 +1,6 @@ productPriceOptions = $productPriceOptions; + $this->arrayManager = $arrayManager; + } + + /** + * {@inheritdoc} + */ + public function modifyData(array $data) + { + return $data; + } + + /** + * {@inheritdoc} + */ + public function modifyMeta(array $meta) + { + $tierPricePath = $this->arrayManager->findPath( + ProductAttributeInterface::CODE_TIER_PRICE, + $meta, + null, + 'children' + ); + if ($tierPricePath) { + $pricePath = $this->arrayManager->findPath( + ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE, + $meta, + $tierPricePath + ); + + if ($pricePath) { + $priceMeta = $this->arrayManager->get($pricePath, $meta); + $updatedStructure = $this->getUpdatedTierPriceStructure($priceMeta); + $meta = $this->arrayManager->remove($pricePath, $meta); + $meta = $this->arrayManager->merge( + $this->arrayManager->slicePath($pricePath, 0, -1), + $meta, + $updatedStructure + ); + } + } + return $meta; + } + + /** + * Get updated tier price structure. + * + * @param array $priceMeta + * @return array + */ + private function getUpdatedTierPriceStructure(array $priceMeta) + { + $priceTypeOptions = $this->productPriceOptions->toOptionArray(); + $firstOption = $priceTypeOptions ? current($priceTypeOptions) : null; + + $priceMeta['arguments']['data']['config']['visible'] = $firstOption + && $firstOption['value'] == ProductPriceOptionsInterface::VALUE_FIXED; + $priceMeta['arguments']['data']['config']['validation'] = [ + 'validate-number' => true, + ]; + return [ + 'price_value' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Container::NAME, + 'formElement' => Container::NAME, + 'dataType' => Price::NAME, + 'component' => 'Magento_Ui/js/form/components/group', + 'label' => __('Price'), + 'enableLabel' => true, + 'dataScope' => '', + 'additionalClasses' => 'control-grouped', + 'sortOrder' => isset($priceMeta['arguments']['data']['config']['sortOrder']) + ? $priceMeta['arguments']['data']['config']['sortOrder'] : 40, + ], + ], + ], + 'children' => [ + ProductAttributeInterface::CODE_TIER_PRICE_FIELD_VALUE_TYPE => [ + 'arguments' => [ + 'data' => [ + 'options' => $priceTypeOptions, + 'config' => [ + 'componentType' => Field::NAME, + 'formElement' => Select::NAME, + 'dataType' => 'text', + 'component' => 'Magento_Catalog/js/tier-price/value-type-select', + 'prices' => [ + ProductPriceOptionsInterface::VALUE_FIXED => '${ $.parentName }.' + . ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE, + ProductPriceOptionsInterface::VALUE_PERCENT => '${ $.parentName }.' + . ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE, + ], + ], + ], + ], + ], + ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE => $priceMeta, + ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Field::NAME, + 'formElement' => Input::NAME, + 'dataType' => Price::NAME, + 'addbefore' => '%', + 'validation' => [ + 'validate-number' => true, + 'less-than-equals-to' => 100 + ], + 'visible' => $firstOption + && $firstOption['value'] == ProductPriceOptionsInterface::VALUE_PERCENT, + ], + ], + ], + ], + 'price_calc' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Container::NAME, + 'component' => 'Magento_Catalog/js/tier-price/percentage-processor', + 'visible' => false + ], + ], + ] + ] + ], + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index b8f13d899be65..6329c338e7c8d 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 9993d4657533a..304739b957220 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -1,7 +1,7 @@ @@ -73,7 +73,6 @@ - @@ -143,6 +142,10 @@ Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Attributes 120 + + Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\TierPrice + 150 + @@ -166,4 +169,14 @@ product_form.product_form + + + Magento\Catalog\Model\Config\Source\Product\Options\TierPrice + + + + + Magento\Catalog\Model\Attribute\ScopeOverriddenValue + + diff --git a/app/code/Magento/Catalog/etc/adminhtml/events.xml b/app/code/Magento/Catalog/etc/adminhtml/events.xml index a77e1e741a4be..034204feff5c9 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/events.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/adminhtml/menu.xml b/app/code/Magento/Catalog/etc/adminhtml/menu.xml index d0f15c930e6d2..ee0d1ec5c4117 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/menu.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/menu.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/adminhtml/routes.xml b/app/code/Magento/Catalog/etc/adminhtml/routes.xml index 8dac88c2a22cd..5deeddb3bb4bd 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/routes.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/routes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 85949a953fc53..c5a1b3686fbe5 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/catalog_attributes.xml b/app/code/Magento/Catalog/etc/catalog_attributes.xml index d822d36eabfee..650652aa94555 100644 --- a/app/code/Magento/Catalog/etc/catalog_attributes.xml +++ b/app/code/Magento/Catalog/etc/catalog_attributes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/catalog_attributes.xsd b/app/code/Magento/Catalog/etc/catalog_attributes.xsd index 00384d783eff1..d95d5a17c258e 100644 --- a/app/code/Magento/Catalog/etc/catalog_attributes.xsd +++ b/app/code/Magento/Catalog/etc/catalog_attributes.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index a86b005be2857..4a8a523e0d55c 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/crontab.xml b/app/code/Magento/Catalog/etc/crontab.xml index 2288ed4ebd8a8..d69ac8f319b5e 100644 --- a/app/code/Magento/Catalog/etc/crontab.xml +++ b/app/code/Magento/Catalog/etc/crontab.xml @@ -1,7 +1,7 @@ @@ -13,5 +13,8 @@ 0 0 * * * + + * * * * * + diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 49ad7d67d7058..ea568c6937ebf 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -1,7 +1,7 @@ @@ -47,6 +47,21 @@ + + + + + + + + + + + + + + + @@ -142,6 +157,11 @@ 9,15,30 + + + 1000 + + 5,10,15,20,25 @@ -833,4 +853,62 @@ Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor + + + + + + + + Magento\Catalog\Model\ResourceModel\Product\StatusBaseSelectProcessor + + + + + + + Magento\Catalog\Model\ResourceModel\Product\Website\SelectProcessor + + + + + + Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor + + + + + + simple + virtual + + + + + + + simple + virtual + bundle + + + + + + + simple + virtual + bundle + + + + + + + simple + virtual + + + + diff --git a/app/code/Magento/Catalog/etc/eav_attributes.xml b/app/code/Magento/Catalog/etc/eav_attributes.xml index c480ea4dd2322..005402937232f 100644 --- a/app/code/Magento/Catalog/etc/eav_attributes.xml +++ b/app/code/Magento/Catalog/etc/eav_attributes.xml @@ -1,7 +1,7 @@ @@ -30,6 +30,7 @@ + diff --git a/app/code/Magento/Catalog/etc/events.xml b/app/code/Magento/Catalog/etc/events.xml index 544abf6b9e069..237382bfa387d 100644 --- a/app/code/Magento/Catalog/etc/events.xml +++ b/app/code/Magento/Catalog/etc/events.xml @@ -1,7 +1,7 @@ @@ -51,4 +51,10 @@ + + + + + + diff --git a/app/code/Magento/Catalog/etc/extension_attributes.xml b/app/code/Magento/Catalog/etc/extension_attributes.xml index 509c3240bb6c8..3136b3df67274 100644 --- a/app/code/Magento/Catalog/etc/extension_attributes.xml +++ b/app/code/Magento/Catalog/etc/extension_attributes.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 9b99bd4a78140..ca1e1e244f49c 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -1,7 +1,7 @@ @@ -18,9 +18,6 @@ Magento\Catalog\Model\ResourceModel\Category\Collection\FetchStrategy - - - true diff --git a/app/code/Magento/Catalog/etc/frontend/events.xml b/app/code/Magento/Catalog/etc/frontend/events.xml index 5ef8c72468314..dd225750f73be 100644 --- a/app/code/Magento/Catalog/etc/frontend/events.xml +++ b/app/code/Magento/Catalog/etc/frontend/events.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/frontend/page_types.xml b/app/code/Magento/Catalog/etc/frontend/page_types.xml index 2557d79a1a49b..8f929046afeef 100644 --- a/app/code/Magento/Catalog/etc/frontend/page_types.xml +++ b/app/code/Magento/Catalog/etc/frontend/page_types.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/frontend/routes.xml b/app/code/Magento/Catalog/etc/frontend/routes.xml index 5adaf604c51d5..d4d52559673d6 100644 --- a/app/code/Magento/Catalog/etc/frontend/routes.xml +++ b/app/code/Magento/Catalog/etc/frontend/routes.xml @@ -1,7 +1,7 @@ @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Catalog/etc/frontend/sections.xml b/app/code/Magento/Catalog/etc/frontend/sections.xml index 7c36594283640..0bc9c63494b33 100644 --- a/app/code/Magento/Catalog/etc/frontend/sections.xml +++ b/app/code/Magento/Catalog/etc/frontend/sections.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/indexer.xml b/app/code/Magento/Catalog/etc/indexer.xml index 88e6da345c393..5c2ca91e525d9 100644 --- a/app/code/Magento/Catalog/etc/indexer.xml +++ b/app/code/Magento/Catalog/etc/indexer.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index 1250b55b96848..571176694547a 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/etc/module.xml @@ -1,12 +1,12 @@ - + diff --git a/app/code/Magento/Catalog/etc/mview.xml b/app/code/Magento/Catalog/etc/mview.xml index d6614e837dde3..4600bb7fad370 100644 --- a/app/code/Magento/Catalog/etc/mview.xml +++ b/app/code/Magento/Catalog/etc/mview.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_options.xml b/app/code/Magento/Catalog/etc/product_options.xml index 48d62d0c2c0ad..43bf4865cb49e 100644 --- a/app/code/Magento/Catalog/etc/product_options.xml +++ b/app/code/Magento/Catalog/etc/product_options.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_options.xsd b/app/code/Magento/Catalog/etc/product_options.xsd index a6bcb74ac2894..18b5934c1410f 100644 --- a/app/code/Magento/Catalog/etc/product_options.xsd +++ b/app/code/Magento/Catalog/etc/product_options.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_options_merged.xsd b/app/code/Magento/Catalog/etc/product_options_merged.xsd index 2a5951c57787d..7b9e6fa2650ec 100644 --- a/app/code/Magento/Catalog/etc/product_options_merged.xsd +++ b/app/code/Magento/Catalog/etc/product_options_merged.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_types.xml b/app/code/Magento/Catalog/etc/product_types.xml index a1516fee38ed5..513f0905b13ce 100644 --- a/app/code/Magento/Catalog/etc/product_types.xml +++ b/app/code/Magento/Catalog/etc/product_types.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_types.xsd b/app/code/Magento/Catalog/etc/product_types.xsd index e0cb33802851e..06999fbeddc7a 100644 --- a/app/code/Magento/Catalog/etc/product_types.xsd +++ b/app/code/Magento/Catalog/etc/product_types.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_types_base.xsd b/app/code/Magento/Catalog/etc/product_types_base.xsd index 94d8b87d167e3..eddd7a6845488 100644 --- a/app/code/Magento/Catalog/etc/product_types_base.xsd +++ b/app/code/Magento/Catalog/etc/product_types_base.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/product_types_merged.xsd b/app/code/Magento/Catalog/etc/product_types_merged.xsd index 1a1a9bfd8214c..1b1d92c163989 100644 --- a/app/code/Magento/Catalog/etc/product_types_merged.xsd +++ b/app/code/Magento/Catalog/etc/product_types_merged.xsd @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/view.xml b/app/code/Magento/Catalog/etc/view.xml index 756888e3b688f..8c7500d9c1374 100644 --- a/app/code/Magento/Catalog/etc/view.xml +++ b/app/code/Magento/Catalog/etc/view.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/webapi.xml b/app/code/Magento/Catalog/etc/webapi.xml index 99670c347a89b..1101a7bc93814 100644 --- a/app/code/Magento/Catalog/etc/webapi.xml +++ b/app/code/Magento/Catalog/etc/webapi.xml @@ -1,7 +1,7 @@ @@ -246,6 +246,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml index 8606cd8de1136..67e74dfbfd44e 100644 --- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml index 9d2f4abfa5b46..cb5273e4aeac5 100644 --- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/etc/widget.xml b/app/code/Magento/Catalog/etc/widget.xml index e9907e5b17700..f54d4af816c09 100644 --- a/app/code/Magento/Catalog/etc/widget.xml +++ b/app/code/Magento/Catalog/etc/widget.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index 2ad2194b36466..94b32f979383d 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -183,7 +183,7 @@ Categories,Categories "Category is not available for requested store.","Category is not available for requested store." "There was a category move error.","There was a category move error." "There was a category move error. %1","There was a category move error. %1" -"You moved the category","You moved the category" +"You moved the category.","You moved the category." "Attribute ""%1"" is required.","Attribute ""%1"" is required." "You saved the category.","You saved the category." "Something went wrong while saving the category.","Something went wrong while saving the category." diff --git a/app/code/Magento/Catalog/registration.php b/app/code/Magento/Catalog/registration.php index 96b9df94d399e..fada27f08c173 100644 --- a/app/code/Magento/Catalog/registration.php +++ b/app/code/Magento/Catalog/registration.php @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_CONFIGURE_ERROR.xml b/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_CONFIGURE_ERROR.xml index 2809386e4f94e..30add348f7d2b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_CONFIGURE_ERROR.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_CONFIGURE_ERROR.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_UPDATE_RESULT.xml b/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_UPDATE_RESULT.xml index a71dd55d3dfc0..ec97c79610237 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_UPDATE_RESULT.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/CATALOG_PRODUCT_COMPOSITE_UPDATE_RESULT.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_add.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_add.xml index 5f376149e96bf..d9c70ae487903 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_add.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_add.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_create.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_create.xml index 0a8b4e2509cd0..02734a674189e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_create.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_create.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_edit.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_edit.xml index 1f975b4a701a3..799c50dfc4756 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_edit.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_category_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_action_attribute_edit.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_action_attribute_edit.xml index be147a4270879..3a073f75eef12 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_action_attribute_edit.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_action_attribute_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertspricegrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertspricegrid.xml index 39b7f82b2e78d..0cd56d138149e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertspricegrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertspricegrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertsstockgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertsstockgrid.xml index 74e558c34cc79..d098da96d3e52 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertsstockgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_alertsstockgrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit.xml index ad6d9f5d4ef94..ddf02a7cdb9c2 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml index 59c5d5cff3158..8f4780d34b17d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml index 5049f5ac8e3f5..a19d29a98720e 100755 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_attribute_edit_popup.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_change_attribute_set.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_change_attribute_set.xml index 82007f774971f..422cf537c3081 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_change_attribute_set.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_change_attribute_set.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml index 6f95ce72a20bf..9c1280a2500df 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml index 808e95a885616..96c66485f2132 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_customoptions.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_customoptions.xml index 064463bd0d613..39781cc8adcf0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_customoptions.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_customoptions.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_edit.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_edit.xml index d8b0ce7c957e0..3375f5b8233f5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_edit.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml index bdcd5da65bbea..194c745e6a65a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml index 7343969a40ba4..e214ccad3dc21 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml index 7ee21e218051f..bad6a5d165535 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml index 0d6dae0c99a87..cb993bc892eac 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_options.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_options.xml index e52d7e7ec1a14..7d88ff2a04384 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_options.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_options.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_optionsimportgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_optionsimportgrid.xml index 699b6084c314a..7f6f62943bbea 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_optionsimportgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_optionsimportgrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml index e1f2eb0403580..6b688eeec2084 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml index 8d2fb11dc18c1..4a306dd725b91 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_reload.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_reload.xml index 82007f774971f..422cf537c3081 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_reload.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_reload.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml index 1dc0de6498cb1..bbbc1e21669e1 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_edit.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_edit.xml index d61a2c344d666..7caf391119166 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_edit.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_edit.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_index.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_index.xml index 23b859d526e10..b25eecbbc2502 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_index.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_index.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml index f7ec295f154a0..ce0b1521d82e6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml index 097649c8c0aa3..83c19659b5135 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js index d2e45cbfb42ee..848d1f1da908c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -17,4 +17,4 @@ var config = { deps: [ 'Magento_Catalog/catalog/product' ] -}; \ No newline at end of file +}; diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml index b1f8197650fd9..eaeed5b12ebaf 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml @@ -1,6 +1,6 @@
    -
    - - - - getStores() as $_store): ?> - - - - - - - getLabelValues() ?> - getStores() as $_store): ?> - - - - -
    getName() ?>
    - getReadOnly()):?> disabled="disabled"/> -
    +
    +
    + + + + getStores() as $_store): ?> + + + + + + + getLabelValues() ?> + getStores() as $_store): ?> + + + + +
    getName() ?>
    + getReadOnly()):?> disabled="disabled"/> +
    +
    diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml index 500126c24b530..1f1dc0925f2c0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml @@ -1,6 +1,6 @@ getStoresSortedBySortOrder(); ?> -
    +
    escapeHtml(__('Manage Options (Values of Your Attribute)')); ?> - -
    +
    +
    diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml index 1c58e3424b1c1..d73e4c6095d9d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml @@ -1,6 +1,6 @@ escapeJs(__('Please select a node.')) ?>' + }); + return; + } + for (i in currentNode.childNodes) { if (currentNode.childNodes[i].id) { child = editSet.currentNode.childNodes[i]; @@ -253,31 +264,40 @@ addGroup : function() { prompt({ + title: "", content: "", value: "", + validation: true, + validationRules: ['required-entry'], + attributesForm: { + novalidate: 'novalidate', + action: '' + }, + attributesField: { + name: 'name', + 'data-validate': '{required:true}', + maxlength: '255' + }, actions: { confirm: function (group_name) { group_name = group_name.strip(); - if( group_name == '' ) { - self.addGroup(); - } else if( group_name != false && group_name != null && group_name != '' ) { - - if (!editSet.validateGroupName(group_name, 0)) { - return; - } - - var newNode = new Ext.tree.TreeNode({ - text : group_name.escapeHTML(), - cls : 'folder', - allowDrop : true, - allowDrag : true - }); - TreePanels.root.appendChild(newNode); - newNode.addListener('beforemove', editSet.groupBeforeMove); - newNode.addListener('beforeinsert', editSet.groupBeforeInsert); - newNode.addListener('beforeappend', editSet.groupBeforeInsert); - newNode.addListener('click', editSet.register); + + if (!editSet.validateGroupName(group_name, 0)) { + return; } + + var newNode = new Ext.tree.TreeNode({ + text : group_name.escapeHTML(), + cls : 'folder', + allowDrop : true, + allowDrag : true + }); + + TreePanels.root.appendChild(newNode); + newNode.addListener('beforemove', editSet.groupBeforeMove); + newNode.addListener('beforeinsert', editSet.groupBeforeInsert); + newNode.addListener('beforeappend', editSet.groupBeforeInsert); + newNode.addListener('click', editSet.register); } } }); @@ -302,7 +322,7 @@ } for (var i=0; i < TreePanels.root.childNodes.length; i++) { if (TreePanels.root.childNodes[i].text.toLowerCase() == name.toLowerCase() && TreePanels.root.childNodes[i].id != exceptNodeId) { - errorText = ''; + errorText = ''; alert({ content: errorText.replace("/name/",name) }); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml index 526d2f98ead9c..2928eff384df6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml @@ -1,5 +1,5 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml index 536e840249b78..cf40ee6d78af6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml index 3b5748da54823..2cdb9f451a86f 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml index 0a2fcb84c996c..767b54e0fdadf 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml index 5d35a9e8be152..cb60254d4d8c7 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml @@ -1,6 +1,6 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml index 5ffacc462ac83..82d2111b986cd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml @@ -1,6 +1,6 @@ @@ -375,19 +375,6 @@ categoryURL Key10 - - ${ $.provider }:data.use_default.url_key - - - - - - - - Use Default - boolean - checkbox - 20 @@ -453,10 +440,9 @@ - admin__field-no-label + admin__field-x-small 170 - - Use Parent Category Settings + Use Parent Category Settings boolean checkbox @@ -464,6 +450,8 @@ 0 0 + Magento_Ui/js/form/element/single-checkbox + toggle @@ -509,20 +497,21 @@ - admin__field-no-label + admin__field-x-small 210 - - Apply Design to Products + Apply Design to Products boolean checkbox - - ns = ${ $.ns }, index = custom_use_parent_settings :checked - 1 0 0 + Magento_Ui/js/form/element/single-checkbox + toggle + + ns = ${ $.ns }, index = custom_use_parent_settings:checked + diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/crosssell_product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/crosssell_product_listing.xml index 965694d7f94c4..c562cf1096fa3 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/crosssell_product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/crosssell_product_listing.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml index 9852ad74121c8..28522ca5c2ba2 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/new_category_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/new_category_form.xml index dbe6aa9eb7d91..a0307886770c6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/new_category_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/new_category_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml index 870304c881627..ab5ab6e288e13 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attributes_grid.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attributes_grid.xml index 4067cd062de6c..b4b625dbdac92 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attributes_grid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attributes_grid.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_custom_options_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_custom_options_listing.xml index 5c7292637129d..2b6db7050d0a9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_custom_options_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_custom_options_listing.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_form.xml index 2db3d337822b6..b99e01147c00d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_form.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml index ec88952af5033..c2aa0b8c69622 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml @@ -1,7 +1,7 @@ @@ -23,6 +23,9 @@ Magento_Ui/js/grid/provider + + filters.store_id + diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/related_product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/related_product_listing.xml index 25350158c5297..55863e0a21c0d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/related_product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/related_product_listing.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/upsell_product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/upsell_product_listing.xml index fc03f128f4617..26703ba03c9a0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/upsell_product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/upsell_product_listing.xml @@ -1,7 +1,7 @@ diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/apply-to-type-switcher.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/apply-to-type-switcher.js index 595638d8ca6ef..13543ade8f726 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/apply-to-type-switcher.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/apply-to-type-switcher.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js index 9c0d986c7e9f2..75cdd9ecf66fd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js @@ -1,7 +1,8 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + /*global alert:true*/ define([ 'jquery', @@ -20,8 +21,10 @@ define([ * @protected */ options: { - maxImageUploadCount : 10 + maxImageUploadCount: 10 }, + + /** @inheritdoc */ _create: function () { var $container = this.element, imageTmpl = mageTemplate(this.element.find('[data-template=image]').html()), @@ -30,22 +33,31 @@ define([ mainClass = 'base-image', maximumImageCount = 5, $fieldCheckBox = $container.closest('[data-attribute-code=image]').find(':checkbox'), - isDefaultChecked = $fieldCheckBox.is(':checked'); + isDefaultChecked = $fieldCheckBox.is(':checked'), + findElement, updateVisibility; if (isDefaultChecked) { $fieldCheckBox.trigger('click'); } - var findElement = function (data) { + /** + * @param {Object} data + * @return {HTMLElement} + */ + findElement = function (data) { return $container.find('.image:not(.image-placeholder)').filter(function () { if (!$(this).data('image')) { return false; } + return $(this).data('image').file === data.file; }).first(); }; - var updateVisibility = function () { + + /** Update image visibility. */ + updateVisibility = function () { var elementsList = $container.find('.image:not(.removed-item)'); + elementsList.each(function (index) { $(this)[index < maximumImageCount ? 'show' : 'hide'](); }); @@ -78,12 +90,13 @@ define([ }); $galleryContainer.on('moveElement', function (event, data) { - var $element = findElement(data.imageData); + var $element = findElement(data.imageData), + $after; if (data.position === 0) { $container.prepend($element); } else { - var $after = $container.find('.image').eq(data.position); + $after = $container.find('.image').eq(data.position); if (!$element.is($after)) { $element.insertAfter($after); @@ -93,8 +106,10 @@ define([ }); $container.on('click', '[data-role=make-base-button]', function (event) { + var data; + event.preventDefault(); - var data = $(event.target).closest('.image').data('image'); + data = $(event.target).closest('.image').data('image'); $galleryContainer.productGallery('setBase', data); }); @@ -108,6 +123,11 @@ define([ items: '.image:not(.image-placeholder)', distance: 8, tolerance: 'pointer', + + /** + * @param {jQuery.Event} event + * @param {Object} data + */ stop: function (event, data) { $galleryContainer.trigger('setPosition', { imageData: data.item.data('image'), @@ -122,6 +142,11 @@ define([ dropZone: $dropPlaceholder.closest('[data-attribute-code]'), acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, maxFileSize: this.element.data('maxFileSize'), + + /** + * @param {jQuery.Event} event + * @param {Object} data + */ done: function (event, data) { $dropPlaceholder.find('.progress-bar').text('').removeClass('in-progress'); @@ -137,13 +162,22 @@ define([ }); } }, - change: function(e, data) { + + /** + * @param {jQuery.Event} e + * @param {Object} data + */ + change: function (e, data) { if (data.files.length > this.options.maxImageUploadCount) { $('body').notification('clear').notification('add', { error: true, - message: $.mage.__('You can\'t upload more than ' + this.options.maxImageUploadCount - + ' images in one time'), - insertMethod: function(message) { + message: $.mage.__('You can\'t upload more than ' + this.options.maxImageUploadCount + + ' images in one time'), + + /** + * @param {*} message + */ + insertMethod: function (message) { $('.page-main-actions').after(message); } }); @@ -151,20 +185,39 @@ define([ return false; } }.bind(this), + + /** + * @param {jQuery.Event} event + * @param {*} data + */ add: function (event, data) { $(this).fileupload('process', data).done(function () { data.submit(); }); }, + + /** + * @param {jQuery.Event} e + * @param {Object} data + */ progress: function (e, data) { var progress = parseInt(data.loaded / data.total * 100, 10); + $dropPlaceholder.find('.progress-bar').addClass('in-progress').text(progress + '%'); }, + + /** + * @param {jQuery.Event} event + */ start: function (event) { var uploaderContainer = $(event.target).closest('.image-placeholder'); uploaderContainer.addClass('loading'); }, + + /** + * @param {jQuery.Event} event + */ stop: function (event) { var uploaderContainer = $(event.target).closest('.image-placeholder'); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/assign-products.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/assign-products.js index ad5f52095f36b..1da9c2c379c37 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/assign-products.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/assign-products.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js index 28bc0734ef033..8c5e5bf1ca966 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js @@ -1,78 +1,86 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /** * Create/edit some category */ + +/* global tree */ define([ 'jquery', 'prototype' ], function (jQuery) { + 'use strict'; + + /** Category submit. */ + var categorySubmit = function () { + var activeTab = $('active_tab_id'), + params = {}, + fields, i,categoryId, isCreating, path, parentId, currentNode, oldClass, newClass; - var categorySubmit = function (url, useAjax) { - var activeTab = $('active_tab_id'); - if (activeTab) { - if (activeTab.tabsJsObject && activeTab.tabsJsObject.tabs('activeAnchor')) { - activeTab.value = activeTab.tabsJsObject.tabs('activeAnchor').prop('id'); + if (activeTab) { + if (activeTab.tabsJsObject && activeTab.tabsJsObject.tabs('activeAnchor')) { + activeTab.value = activeTab.tabsJsObject.tabs('activeAnchor').prop('id'); + } } - } - var params = {}; - var fields = $('category_edit_form').getElementsBySelector('input', 'select'); - for (var i=0; i').modal({ - title: $.mage.__('New Attribute'), + title: $.mage.__('New Attribute'), type: 'slide', buttons: [], + + /** @inheritdoc */ opened: function () { $(this).parent().addClass('modal-content-new-attribute'); self.iframe = $('', + 'Tag is removed SomeObject', ], [ 'Tag is removed SomeScript', @@ -96,6 +97,7 @@ public function filterDataProvider() 'Tag is removed SomeLink', 'Tag is removed SomeFrame', 'Tag is removed SomeIFrame', + 'Tag is removed SomeObject', ], ], 'Base64' => [ diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/InputTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/InputTest.php index 1f1d2fb18fdc5..0d83b43e4f3bc 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/InputTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/InputTest.php @@ -1,6 +1,6 @@ json = $json ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serialize = $serialize ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Serialize::class); + parent::__construct( + $context, + $registry, + $resource, + $resourceCollection, + $data + ); + } + /** * Init resource model * Set flag_code if it is specified in arguments @@ -68,9 +113,13 @@ public function beforeSave() public function getFlagData() { if ($this->hasFlagData()) { - return unserialize($this->getData('flag_data')); - } else { - return null; + $flagData = $this->getData('flag_data'); + $data = $this->json->unserialize($flagData); + if (JSON_ERROR_NONE == json_last_error()) { + return $data; + } else { + return $this->serialize->unserialize($flagData); + } } } @@ -82,7 +131,7 @@ public function getFlagData() */ public function setFlagData($value) { - return $this->setData('flag_data', serialize($value)); + return $this->setData('flag_data', $this->json->serialize($value)); } /** diff --git a/lib/internal/Magento/Framework/Flag/FlagResource.php b/lib/internal/Magento/Framework/Flag/FlagResource.php index 0b94b37a2fc2a..fe12e7e85d600 100644 --- a/lib/internal/Magento/Framework/Flag/FlagResource.php +++ b/lib/internal/Magento/Framework/Flag/FlagResource.php @@ -1,6 +1,6 @@ (CURLPROTO_HTTP + | CURLPROTO_HTTPS + | CURLPROTO_FTP + | CURLPROTO_FTPS + ), + 'verifypeer' => true, + 'verifyhost' => 2 + ]; /** * Curl handle @@ -41,7 +49,11 @@ class Curl implements \Zend_Http_Client_Adapter_Interface 'ssl_cert' => CURLOPT_SSLCERT, 'userpwd' => CURLOPT_USERPWD, 'useragent' => CURLOPT_USERAGENT, - 'referer' => CURLOPT_REFERER + 'referer' => CURLOPT_REFERER, + 'protocols' => CURLOPT_PROTOCOLS, + 'verifypeer' => CURLOPT_SSL_VERIFYPEER, + 'verifyhost' => CURLOPT_SSL_VERIFYHOST, + 'sslversion' => CURLOPT_SSLVERSION, ]; /** @@ -55,8 +67,6 @@ class Curl implements \Zend_Http_Client_Adapter_Interface * Apply current configuration array to transport resource * * @return \Magento\Framework\HTTP\Adapter\Curl - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _applyConfig() { @@ -65,22 +75,28 @@ protected function _applyConfig() curl_setopt($this->_getResource(), $option, $value); } - if (empty($this->_config)) { - return $this; + // apply config options + foreach ($this->getDefaultConfig() as $option => $value) { + curl_setopt($this->_getResource(), $option, $value); } - $verifyPeer = isset($this->_config['verifypeer']) ? $this->_config['verifypeer'] : true; - curl_setopt($this->_getResource(), CURLOPT_SSL_VERIFYPEER, $verifyPeer); - - $verifyHost = isset($this->_config['verifyhost']) ? $this->_config['verifyhost'] : 2; - curl_setopt($this->_getResource(), CURLOPT_SSL_VERIFYHOST, $verifyHost); + return $this; + } - foreach ($this->_config as $param => $curlOption) { + /** + * Get default options + * + * @return array + */ + private function getDefaultConfig() + { + $config = []; + foreach (array_keys($this->_config) as $param) { if (array_key_exists($param, $this->_allowedParams)) { - curl_setopt($this->_getResource(), $this->_allowedParams[$param], $this->_config[$param]); + $config[$this->_allowedParams[$param]] = $this->_config[$param]; } } - return $this; + return $config; } /** @@ -116,7 +132,9 @@ public function addOption($option, $value) */ public function setConfig($config = []) { - $this->_config = $config; + foreach ($config as $key => $value) { + $this->_config[$key] = $value; + } return $this; } @@ -160,6 +178,9 @@ public function write($method, $url, $http_ver = '1.1', $headers = [], $body = ' curl_setopt($this->_getResource(), CURLOPT_POSTFIELDS, $body); } elseif ($method == \Zend_Http_Client::GET) { curl_setopt($this->_getResource(), CURLOPT_HTTPGET, true); + } elseif ($method == \Zend_Http_Client::PUT) { + curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, \Zend_Http_Client::PUT); + curl_setopt($this->_getResource(), CURLOPT_POSTFIELDS, $body); } if (is_array($headers)) { @@ -268,6 +289,13 @@ public function multiRequest($urls, $options = []) $multihandle = curl_multi_init(); + // add default parameters + foreach ($this->getDefaultConfig() as $defaultOption => $defaultValue) { + if (!isset($options[$defaultOption])) { + $options[$defaultOption] = $defaultValue; + } + } + foreach ($urls as $key => $url) { $handles[$key] = curl_init(); curl_setopt($handles[$key], CURLOPT_URL, $url); diff --git a/lib/internal/Magento/Framework/HTTP/Adapter/FileTransferFactory.php b/lib/internal/Magento/Framework/HTTP/Adapter/FileTransferFactory.php index 32a5478a45f60..2fb852910cb87 100644 --- a/lib/internal/Magento/Framework/HTTP/Adapter/FileTransferFactory.php +++ b/lib/internal/Magento/Framework/HTTP/Adapter/FileTransferFactory.php @@ -1,6 +1,6 @@ + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Curl implements \Magento\Framework\HTTP\ClientInterface { @@ -16,7 +17,7 @@ class Curl implements \Magento\Framework\HTTP\ClientInterface * Max supported protocol by curl CURL_SSLVERSION_TLSv1_2 * @var int */ - private static $sslVersion = 6; + private $sslVersion; /** * Hostname @@ -86,7 +87,7 @@ class Curl implements \Magento\Framework\HTTP\ClientInterface /** * Curl - * @var object + * @var resource */ protected $_ch; @@ -117,10 +118,11 @@ public function setTimeout($value) } /** - * Constructor + * @param int|null $sslVersion */ - public function __construct() + public function __construct($sslVersion = null) { + $this->sslVersion = $sslVersion; } /** @@ -228,8 +230,11 @@ public function get($uri) /** * Make POST request * + * String type was added to parameter $param in order to support sending JSON or XML requests. + * This feature was added base on Community Pull Request https://github.com/magento/magento2/pull/8373 + * * @param string $uri - * @param array $params + * @param array|string $params * @return void * * @see \Magento\Framework\HTTP\Client#post($uri, $params) @@ -333,9 +338,13 @@ public function getStatus() /** * Make request + * + * String type was added to parameter $param in order to support sending JSON or XML requests. + * This feature was added base on Community Pull Request https://github.com/magento/magento2/pull/8373 + * * @param string $method * @param string $uri - * @param array $params + * @param array|string $params - use $params as a string in case of JSON or XML POST request. * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -346,7 +355,7 @@ protected function makeRequest($method, $uri, $params = []) $this->curlOption(CURLOPT_URL, $uri); if ($method == 'POST') { $this->curlOption(CURLOPT_POST, 1); - $this->curlOption(CURLOPT_POSTFIELDS, http_build_query($params)); + $this->curlOption(CURLOPT_POSTFIELDS, is_array($params) ? http_build_query($params) : $params); } elseif ($method == "GET") { $this->curlOption(CURLOPT_HTTPGET, 1); } else { @@ -377,10 +386,11 @@ protected function makeRequest($method, $uri, $params = []) $this->curlOption(CURLOPT_PORT, $this->_port); } - //$this->curlOption(CURLOPT_HEADER, 1); $this->curlOption(CURLOPT_RETURNTRANSFER, 1); $this->curlOption(CURLOPT_HEADERFUNCTION, [$this, 'parseHeaders']); - $this->curlOption(CURLOPT_SSLVERSION, self::$sslVersion); + if ($this->sslVersion !== null) { + $this->curlOption(CURLOPT_SSLVERSION, $this->sslVersion); + } if (count($this->_curlUserOptions)) { foreach ($this->_curlUserOptions as $k => $v) { @@ -415,6 +425,7 @@ public function doError($string) * @param resource $ch curl handle, not needed * @param string $data * @return int + * @throws \Exception * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function parseHeaders($ch, $data) @@ -422,11 +433,10 @@ protected function parseHeaders($ch, $data) if ($this->_headerCount == 0) { $line = explode(" ", trim($data), 3); if (count($line) != 3) { - return $this->doError("Invalid response line returned from server: " . $data); + $this->doError("Invalid response line returned from server: " . $data); } $this->_responseStatus = intval($line[1]); } else { - //var_dump($data); $name = $value = ''; $out = explode(": ", trim($data), 2); if (count($out) == 2) { diff --git a/lib/internal/Magento/Framework/HTTP/Client/Socket.php b/lib/internal/Magento/Framework/HTTP/Client/Socket.php index 2f6a34b06700b..75cdb61c793b3 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Socket.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Socket.php @@ -1,6 +1,6 @@ _saveAlpha($newImage); + } + // fill new image with required color $this->_fillBackgroundColor($newImage); diff --git a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php index ffdde07768022..63fe81f41cc2a 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +++ b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php @@ -1,6 +1,6 @@ compositeImage( + $this->_imageHandler, + \Imagick::COMPOSITE_COPYOPACITY, + $dims['dst']['x'], + $dims['dst']['y'] + ); + $newImage->compositeImage( $this->_imageHandler, \Imagick::COMPOSITE_OVER, diff --git a/lib/internal/Magento/Framework/Image/AdapterFactory.php b/lib/internal/Magento/Framework/Image/AdapterFactory.php index e1eee4dfdbbe4..c18180814a28b 100644 --- a/lib/internal/Magento/Framework/Image/AdapterFactory.php +++ b/lib/internal/Magento/Framework/Image/AdapterFactory.php @@ -1,6 +1,6 @@ ['type' => Table::TYPE_TEXT, 'size' => 255], @@ -71,11 +75,13 @@ class Base implements ActionInterface /** * @var array + * @deprecated */ protected $filterColumns; /** * @var array + * @deprecated */ protected $searchColumns; @@ -96,6 +102,7 @@ class Base implements ActionInterface /** * @var String + * @deprecated */ protected $string; @@ -106,11 +113,13 @@ class Base implements ActionInterface /** * @var array + * @deprecated */ protected $filterable = []; /** * @var array + * @deprecated */ protected $searchable = []; @@ -272,6 +281,7 @@ protected function getPrimaryFieldset() protected function createResultCollection() { $select = $this->getPrimaryResource()->getSelect(); + $select->reset(\Magento\Framework\DB\Select::COLUMNS); $select->columns($this->getPrimaryResource()->getIdFieldName()); foreach ($this->data['fieldsets'] as $fieldset) { if (isset($fieldset['references'])) { @@ -342,6 +352,8 @@ protected function prepareFields() * * @param array $field * @return void + * + * @deprecated */ protected function saveFieldByType($field) { diff --git a/lib/internal/Magento/Framework/Indexer/Action/Dummy.php b/lib/internal/Magento/Framework/Indexer/Action/Dummy.php index e0531b31bd61a..fe079b9f89794 100644 --- a/lib/internal/Magento/Framework/Indexer/Action/Dummy.php +++ b/lib/internal/Magento/Framework/Indexer/Action/Dummy.php @@ -1,6 +1,6 @@ createResultCollection() - : $this->createResultCollection()->addFieldToFilter($this->getPrimaryResource()->getRowIdFieldName(), $ids); + : $this->createResultCollection()->addFieldToFilter($this->getPrimaryResource()->getIdFieldName(), $ids); } } diff --git a/lib/internal/Magento/Framework/Indexer/ActionFactory.php b/lib/internal/Magento/Framework/Indexer/ActionFactory.php index dbd7794244ae4..cf67c059b7dcc 100644 --- a/lib/internal/Magento/Framework/Indexer/ActionFactory.php +++ b/lib/internal/Magento/Framework/Indexer/ActionFactory.php @@ -1,6 +1,6 @@ nodeName) { case 'title': - $data['title'] = $this->getTranslatedNodeValue($childNode); + $data['title'] = $childNode->nodeValue; break; case 'description': - $data['description'] = $this->getTranslatedNodeValue($childNode); + $data['description'] = $childNode->nodeValue; break; case 'saveHandler': $data['saveHandler'] = $this->getAttributeValue($childNode, 'class'); @@ -207,6 +207,7 @@ protected function convertField(\DOMElement $node, $data) * * @param \DOMNode $node * @return string + * @deprecated */ protected function getTranslatedNodeValue(\DOMNode $node) { diff --git a/lib/internal/Magento/Framework/Indexer/Config/Reader.php b/lib/internal/Magento/Framework/Indexer/Config/Reader.php index 1179c2df98d49..a3ee733d783ca 100644 --- a/lib/internal/Magento/Framework/Indexer/Config/Reader.php +++ b/lib/internal/Magento/Framework/Indexer/Config/Reader.php @@ -1,6 +1,6 @@ $documentValue) { $batch[$documentName] = $documentValue; - if (++$i >= $size) { - $items[] = $batch; + if (++$i == $size) { + yield $batch; $i = 0; $batch = []; } } if (count($batch) > 0) { - $items[] = $batch; + yield $batch; } - return $items; } } diff --git a/lib/internal/Magento/Framework/Indexer/SaveHandler/Grid.php b/lib/internal/Magento/Framework/Indexer/SaveHandler/Grid.php index ddce47a9b21d6..e183c2946662d 100644 --- a/lib/internal/Magento/Framework/Indexer/SaveHandler/Grid.php +++ b/lib/internal/Magento/Framework/Indexer/SaveHandler/Grid.php @@ -1,6 +1,6 @@ getName() . $dimension->getValue(); } } + return $this->resource->getTableName(implode('_', $tableNameParts)); } @@ -63,10 +64,12 @@ public function resolve($index, array $dimensions) */ private function getScopeId($dimension) { - if (is_numeric($dimension->getValue())) { - return $dimension->getValue(); - } else { - return $this->scopeResolver->getScope($dimension->getValue())->getId(); + $scopeId = $dimension->getValue(); + + if (!is_numeric($scopeId)) { + $scopeId = $this->scopeResolver->getScope($scopeId)->getId(); } + + return $scopeId; } } diff --git a/lib/internal/Magento/Framework/Indexer/StateInterface.php b/lib/internal/Magento/Framework/Indexer/StateInterface.php index 28c8bb52911d2..7f501aed3bc50 100644 --- a/lib/internal/Magento/Framework/Indexer/StateInterface.php +++ b/lib/internal/Magento/Framework/Indexer/StateInterface.php @@ -1,6 +1,6 @@ assertSame($expected, $this->object->getItems($items, $size)); + $data = $this->object->getItems($items, $size); + $this->assertSame($expected, iterator_to_array($data)); } /** diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php index be8591c3032bd..8072e17f7de70 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/Config/ConverterTest.php @@ -1,6 +1,6 @@ @@ -18,4 +18,4 @@ Indexer public name three Indexer public description three - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_merged_two.xml b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_merged_two.xml index fc3b526f5f6a2..4e6cccae3fe2b 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_merged_two.xml +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_merged_two.xml @@ -1,7 +1,7 @@ @@ -14,4 +14,4 @@ Indexer public name three Indexer public description three - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_one.xml b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_one.xml index 68b7f9a6ad3f8..7c48afc403d54 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_one.xml +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_one.xml @@ -1,7 +1,7 @@ @@ -10,4 +10,4 @@ Indexer public name one Indexer public description one - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_three.xml b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_three.xml index fc3b526f5f6a2..4e6cccae3fe2b 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_three.xml +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_three.xml @@ -1,7 +1,7 @@ @@ -14,4 +14,4 @@ Indexer public name three Indexer public description three - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_two.xml b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_two.xml index f1c73990be463..e92913cd191fc 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_two.xml +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/indexer_two.xml @@ -1,7 +1,7 @@ @@ -14,4 +14,4 @@ Indexer public name three Indexer public description three - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/invalidIndexerXmlArray.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/invalidIndexerXmlArray.php index c7352fdf98455..eb98346f3e88f 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/invalidIndexerXmlArray.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/_files/invalidIndexerXmlArray.php @@ -1,6 +1,6 @@ @@ -14,4 +14,4 @@ Indexer public name Indexer public description - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd index e4a33c72fe883..f196cc1f32a2d 100644 --- a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd +++ b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd b/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd index 91887a4e4ddcb..054cbaf141b0c 100644 --- a/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd +++ b/lib/internal/Magento/Framework/Indexer/etc/indexer_merged.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 0c920717bdf38..701532fdd04ad 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -1,6 +1,6 @@ _omConfig = $omConfig; $this->_relations = $relations; @@ -95,10 +107,11 @@ public function __construct( $this->_cacheId = $cacheId; $this->_reader = $reader; $this->_scopeList = $scopeList; - + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(Serialize::class); $intercepted = $this->_cache->load($this->_cacheId); if ($intercepted !== false) { - $this->_intercepted = unserialize($intercepted); + $this->_intercepted = $this->serializer->unserialize($intercepted); } else { $this->initialize($this->_classDefinitions->getClasses()); } @@ -129,7 +142,7 @@ public function initialize($classDefinitions = []) foreach ($classDefinitions as $class) { $this->hasPlugins($class); } - $this->_cache->save(serialize($this->_intercepted), $this->_cacheId); + $this->_cache->save($this->serializer->serialize($this->_intercepted), $this->_cacheId); } /** diff --git a/lib/internal/Magento/Framework/Interception/ConfigInterface.php b/lib/internal/Magento/Framework/Interception/ConfigInterface.php index d3e6e4daa2ffe..e6af6296440f3 100644 --- a/lib/internal/Magento/Framework/Interception/ConfigInterface.php +++ b/lib/internal/Magento/Framework/Interception/ConfigInterface.php @@ -2,7 +2,7 @@ /** * Interception config. Tells whether plugins have been added for type. * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Interception; diff --git a/lib/internal/Magento/Framework/Interception/Definition/Compiled.php b/lib/internal/Magento/Framework/Interception/Definition/Compiled.php deleted file mode 100644 index 6fbe9c99dce86..0000000000000 --- a/lib/internal/Magento/Framework/Interception/Definition/Compiled.php +++ /dev/null @@ -1,39 +0,0 @@ -_definitions = $definitions; - } - - /** - * Retrieve list of methods - * - * @param string $type - * @return string[] - */ - public function getMethodList($type) - { - return $this->_definitions[$type]; - } -} diff --git a/lib/internal/Magento/Framework/Interception/Definition/Runtime.php b/lib/internal/Magento/Framework/Interception/Definition/Runtime.php index 6da06b63bed24..b3d5fa8d04412 100644 --- a/lib/internal/Magento/Framework/Interception/Definition/Runtime.php +++ b/lib/internal/Magento/Framework/Interception/Definition/Runtime.php @@ -3,7 +3,7 @@ * \Reflection based plugin method list. Uses reflection to retrieve list of interception methods defined in plugin. * Should be only used in development mode, because it reads method list on every request which is expensive. * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Interception\Definition; diff --git a/lib/internal/Magento/Framework/Interception/DefinitionInterface.php b/lib/internal/Magento/Framework/Interception/DefinitionInterface.php index 72941ba8282fa..f9fd3e2c15f57 100644 --- a/lib/internal/Magento/Framework/Interception/DefinitionInterface.php +++ b/lib/internal/Magento/Framework/Interception/DefinitionInterface.php @@ -2,7 +2,7 @@ /** * Plugin method definitions. Provide the list of interception methods in specified plugin. * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Interception; diff --git a/lib/internal/Magento/Framework/Interception/Interceptor.php b/lib/internal/Magento/Framework/Interception/Interceptor.php index 9aa00e1c75175..359d8902af941 100644 --- a/lib/internal/Magento/Framework/Interception/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Interceptor.php @@ -1,6 +1,6 @@ serializer = $serializer ?: $objectManager->get(Serialize::class); + parent::__construct($reader, $configScope, $cache, $cacheId, $this->serializer); $this->_omConfig = $omConfig; $this->_relations = $relations; $this->_definitions = $definitions; @@ -149,6 +166,7 @@ protected function _inheritPlugins($type) } $this->_inherited[$type] = null; if (is_array($plugins) && count($plugins)) { + $this->filterPlugins($plugins); uasort($plugins, [$this, '_sort']); $this->trimInstanceStartingBackslash($plugins); $this->_inherited[$type] = $plugins; @@ -269,9 +287,9 @@ protected function _loadScopedData() $cacheId = implode('|', $this->_scopePriorityScheme) . "|" . $this->_cacheId; $data = $this->_cache->load($cacheId); if ($data) { - list($this->_data, $this->_inherited, $this->_processed) = unserialize($data); - foreach ($this->_scopePriorityScheme as $scope) { - $this->_loadedScopes[$scope] = true; + list($this->_data, $this->_inherited, $this->_processed) = $this->serializer->unserialize($data); + foreach ($this->_scopePriorityScheme as $scopeCode) { + $this->_loadedScopes[$scopeCode] = true; } } else { $virtualTypes = []; @@ -279,18 +297,17 @@ protected function _loadScopedData() if (false == isset($this->_loadedScopes[$scopeCode])) { $data = $this->_reader->read($scopeCode); unset($data['preferences']); - if (!count($data)) { - continue; - } - $this->_inherited = []; - $this->_processed = []; - $this->merge($data); - $this->_loadedScopes[$scopeCode] = true; - foreach ($data as $class => $config) { - if (isset($config['type'])) { - $virtualTypes[] = $class; + if (count($data) > 0) { + $this->_inherited = []; + $this->_processed = []; + $this->merge($data); + foreach ($data as $class => $config) { + if (isset($config['type'])) { + $virtualTypes[] = $class; + } } } + $this->_loadedScopes[$scopeCode] = true; } if ($this->isCurrentScope($scopeCode)) { break; @@ -302,7 +319,10 @@ protected function _loadScopedData() foreach ($this->getClassDefinitions() as $class) { $this->_inheritPlugins($class); } - $this->_cache->save(serialize([$this->_data, $this->_inherited, $this->_processed]), $cacheId); + $this->_cache->save( + $this->serializer->serialize([$this->_data, $this->_inherited, $this->_processed]), + $cacheId + ); } $this->_pluginInstances = []; } @@ -348,4 +368,34 @@ public function merge(array $config) } } } + + /** + * Remove from list not existing plugins + * + * @param array $plugins + * @return void + */ + private function filterPlugins(array &$plugins) + { + foreach ($plugins as $name => $plugin) { + if (empty($plugin['instance'])) { + unset($plugins[$name]); + $this->getLogger()->info("Reference to undeclared plugin with name '{$name}'."); + } + } + } + + /** + * Get logger + * + * @return \Psr\Log\LoggerInterface + * @deprecated + */ + private function getLogger() + { + if ($this->logger === null) { + $this->logger = $this->_objectManager->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } } diff --git a/lib/internal/Magento/Framework/Interception/PluginListInterface.php b/lib/internal/Magento/Framework/Interception/PluginListInterface.php index eeb8735bf74ea..64b8549893885 100644 --- a/lib/internal/Magento/Framework/Interception/PluginListInterface.php +++ b/lib/internal/Magento/Framework/Interception/PluginListInterface.php @@ -2,7 +2,7 @@ /** * List of plugins configured in application * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Interception; diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php index 4c26b41a1547a..43ff5985ec383 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php @@ -1,6 +1,6 @@ relationsMock = $this->getMockForAbstractClass( \Magento\Framework\ObjectManager\RelationsInterface::class ); + $this->serializerMock = $this->getMock(SerializerInterface::class); + $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); } /** @@ -131,14 +141,22 @@ public function testHasPluginsWhenDataIsNotCached($expectedResult, $type, $entit $this->relationsMock->expects($this->any())->method('has')->will($this->returnValue($expectedResult)); $this->relationsMock->expects($this->any())->method('getParents')->will($this->returnValue($entityParents)); - $model = new \Magento\Framework\Interception\Config\Config( - $this->readerMock, - $this->configScopeMock, - $this->cacheMock, - $this->relationsMock, - $this->omConfigMock, - $this->definitionMock, - 'interception' + $this->serializerMock->expects($this->once()) + ->method('serialize'); + + $this->serializerMock->expects($this->never())->method('unserialize'); + + $model = $this->objectManagerHelper->getObject( + \Magento\Framework\Interception\Config\Config::class, + [ + 'reader' => $this->readerMock, + 'scopeList' => $this->configScopeMock, + 'cache' => $this->cacheMock, + 'relations' => $this->relationsMock, + 'omConfig' => $this->omConfigMock, + 'classDefinitions' => $this->definitionMock, + 'serializer' => $this->serializerMock, + ] ); $this->assertEquals($expectedResult, $model->hasPlugins($type)); @@ -163,18 +181,32 @@ public function testHasPluginsWhenDataIsCached($expectedResult, $type) ]; $this->readerMock->expects($this->never())->method('read'); $this->cacheMock->expects($this->never())->method('save'); + $serializedValue = 'serializedData'; $this->cacheMock->expects($this->any()) ->method('load') ->with($cacheId) - ->will($this->returnValue(serialize($interceptionData))); - $model = new \Magento\Framework\Interception\Config\Config( - $this->readerMock, - $this->configScopeMock, - $this->cacheMock, - new \Magento\Framework\ObjectManager\Relations\Runtime(), - $this->omConfigMock, - $this->definitionMock, - $cacheId + ->will($this->returnValue($serializedValue)); + + $this->serializerMock->expects($this->never())->method('serialize'); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with($serializedValue) + ->willReturn($interceptionData); + + $model = $this->objectManagerHelper->getObject( + \Magento\Framework\Interception\Config\Config::class, + [ + 'reader' => $this->readerMock, + 'scopeList' => $this->configScopeMock, + 'cache' => $this->cacheMock, + 'relations' => $this->objectManagerHelper->getObject( + \Magento\Framework\ObjectManager\Relations\Runtime::class + ), + 'omConfig' => $this->omConfigMock, + 'classDefinitions' => $this->definitionMock, + 'cacheId' => $cacheId, + 'serializer' => $this->serializerMock, + ] ); $this->assertEquals($expectedResult, $model->hasPlugins($type)); diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Custom/Module/Model/InterfaceValidator/Item.php b/lib/internal/Magento/Framework/Interception/Test/Unit/Custom/Module/Model/InterfaceValidator/Item.php index 31a811611fbc5..256c495de378c 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/Custom/Module/Model/InterfaceValidator/Item.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Custom/Module/Model/InterfaceValidator/Item.php @@ -1,6 +1,6 @@ 'definitions']; - - /** - * @covers \Magento\Framework\Interception\Definition\Compiled::getMethodList - * @covers \Magento\Framework\Interception\Definition\Compiled::__construct - */ - public function testGetMethodList() - { - $model = new \Magento\Framework\Interception\Definition\Compiled($this->_definitions); - $this->assertEquals('definitions', $model->getMethodList('type')); - } -} diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/ObjectManager/Config/DeveloperTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/ObjectManager/Config/DeveloperTest.php index 45a125252c733..3f84e0e30ff99 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/ObjectManager/Config/DeveloperTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/ObjectManager/Config/DeveloperTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Framework\ObjectManager\Config\Reader\Dom::class, [], [], '', false); $readerMock->expects($this->any())->method('read')->will($this->returnValueMap($readerMap)); - $this->_configScopeMock = $this->getMock(\Magento\Framework\Config\ScopeInterface::class); - $this->_cacheMock = $this->getMock(\Magento\Framework\Config\CacheInterface::class); + $this->configScopeMock = $this->getMock(\Magento\Framework\Config\ScopeInterface::class); + $this->cacheMock = $this->getMock(\Magento\Framework\Config\CacheInterface::class); // turn cache off - $this->_cacheMock->expects($this->any()) + $this->cacheMock->expects($this->any()) ->method('get') ->will($this->returnValue(false)); @@ -59,62 +72,76 @@ protected function setUp() $omConfigMock->expects($this->any())->method('getOriginalInstanceType')->will($this->returnArgument(0)); - $this->_objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); + $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class); + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnArgument(0); + $this->serializerMock = $this->getMock(SerializerInterface::class); $definitions = new \Magento\Framework\ObjectManager\Definition\Runtime(); - $this->_model = new \Magento\Framework\Interception\PluginList\PluginList( - $readerMock, - $this->_configScopeMock, - $this->_cacheMock, - new \Magento\Framework\ObjectManager\Relations\Runtime(), - $omConfigMock, - new \Magento\Framework\Interception\Definition\Runtime(), - $this->_objectManagerMock, - $definitions, - ['global'], - 'interception' + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->object = $objectManagerHelper->getObject( + \Magento\Framework\Interception\PluginList\PluginList::class, + [ + 'reader' => $readerMock, + 'configScope' => $this->configScopeMock, + 'cache' => $this->cacheMock, + 'relations' => new \Magento\Framework\ObjectManager\Relations\Runtime(), + 'omConfig' => $omConfigMock, + 'definitions' => new \Magento\Framework\Interception\Definition\Runtime(), + 'objectManager' => $this->objectManagerMock, + 'classDefinitions' => $definitions, + 'scopePriorityScheme' => ['global'], + 'cacheId' => 'interception', + 'serializer' => $this->serializerMock + ] + ); + + $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->object, + 'logger', + $this->loggerMock ); } public function testGetPlugin() { - $this->_configScopeMock->expects($this->any())->method('getCurrentScope')->will($this->returnValue('backend')); - $this->_model->getNext(\Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, 'getName'); - $this->_model->getNext( + $this->configScopeMock->expects($this->any())->method('getCurrentScope')->will($this->returnValue('backend')); + $this->object->getNext(\Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, 'getName'); + $this->object->getNext( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer::class, 'getName' ); - $this->_model->getNext( + $this->object->getNext( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash::class, 'getName' ); - $this->assertEquals( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple::class, - $this->_model->getPlugin( + $this->object->getPlugin( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, 'simple_plugin' ) ); $this->assertEquals( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced::class, - $this->_model->getPlugin( + $this->object->getPlugin( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, 'advanced_plugin' ) ); $this->assertEquals( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainerPlugin\Simple::class, - $this->_model->getPlugin( + $this->object->getPlugin( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer::class, 'simple_plugin' ) ); $this->assertEquals( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash\Plugin::class, - $this->_model->getPlugin( + $this->object->getPlugin( \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash::class, 'simple_plugin' ) @@ -131,14 +158,14 @@ public function testGetPlugin() */ public function testGetPlugins($expectedResult, $type, $method, $scopeCode, $code = '__self') { - $this->_configScopeMock->expects( + $this->configScopeMock->expects( $this->any() )->method( 'getCurrentScope' )->will( $this->returnValue($scopeCode) ); - $this->assertEquals($expectedResult, $this->_model->getNext($type, $method, $code)); + $this->assertEquals($expectedResult, $this->object->getNext($type, $method, $code)); } /** @@ -206,11 +233,42 @@ public function getPluginsDataProvider() */ public function testInheritPluginsWithNonExistingClass() { - $this->_configScopeMock->expects($this->any()) + $this->configScopeMock->expects($this->any()) ->method('getCurrentScope') ->will($this->returnValue('frontend')); - $this->_model->getNext('SomeType', 'someMethod'); + $this->object->getNext('SomeType', 'someMethod'); + } + + public function testLoadScopedDataNotCached() + { + $this->configScopeMock->expects($this->exactly(3)) + ->method('getCurrentScope') + ->will($this->returnValue('scope')); + $this->serializerMock->expects($this->once()) + ->method('serialize'); + $this->serializerMock->expects($this->never()) + ->method('unserialize'); + $this->cacheMock->expects($this->once()) + ->method('save'); + + $this->assertEquals(null, $this->object->getNext('Type', 'method')); + } + + /** + * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext + * @covers \Magento\Framework\Interception\PluginList\PluginList::_inheritPlugins + */ + public function testInheritPluginsWithNotExistingPlugin() + { + $this->loggerMock->expects($this->once()) + ->method('info') + ->with("Reference to undeclared plugin with name 'simple_plugin'."); + $this->configScopeMock->expects($this->any()) + ->method('getCurrentScope') + ->will($this->returnValue('frontend')); + + $this->assertNull($this->object->getNext('typeWithoutInstance', 'someMethod')); } /** @@ -219,17 +277,52 @@ public function testInheritPluginsWithNonExistingClass() */ public function testLoadScopedDataCached() { - $this->_configScopeMock->expects($this->once()) + $this->configScopeMock->expects($this->once()) ->method('getCurrentScope') ->will($this->returnValue('scope')); $data = [['key'], ['key'], ['key']]; + $serializedData = 'serialized data'; - $this->_cacheMock->expects($this->once()) + $this->serializerMock->expects($this->never()) + ->method('serialize'); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn($data); + $this->cacheMock->expects($this->once()) ->method('load') ->with('global|scope|interception') - ->will($this->returnValue(serialize($data))); + ->willReturn($serializedData); + + $this->assertEquals(null, $this->object->getNext('Type', 'method')); + } + + /** + * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext + * @covers \Magento\Framework\Interception\PluginList\PluginList::_loadScopedData + */ + public function testLoadScopeDataWithEmptyData() + { + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->will($this->returnArgument(0)); + $this->configScopeMock->expects($this->any()) + ->method('getCurrentScope') + ->will($this->returnValue('emptyscope')); - $this->assertEquals(null, $this->_model->getNext('Type', 'method')); + $this->assertEquals( + [4 => ['simple_plugin']], + $this->object->getNext( + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, + 'getName' + ) + ); + $this->assertEquals( + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple::class, + $this->object->getPlugin( + \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, + 'simple_plugin' + ) + ); } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php index 832a5a67599da..faca7a24bfff8 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php @@ -1,6 +1,6 @@ 'NonExistingPluginClass', ], ], + ], + 'typeWithoutInstance' => [ + 'plugins' => [ + 'simple_plugin' => [], + ], ] ] + ], + [ + 'emptyscope', + [ + + ] ] ]; diff --git a/lib/internal/Magento/Framework/Intl/DateTimeFactory.php b/lib/internal/Magento/Framework/Intl/DateTimeFactory.php index 6d45f9503d005..87d27b1d085ed 100644 --- a/lib/internal/Magento/Framework/Intl/DateTimeFactory.php +++ b/lib/internal/Magento/Framework/Intl/DateTimeFactory.php @@ -1,6 +1,6 @@ _data[self::CUSTOM_ATTRIBUTES][$key]) + ? $this->_data[self::CUSTOM_ATTRIBUTES][$key] + : null; if ($data instanceof \Magento\Framework\Api\AttributeValue) { $data = $data->getValue(); } + if (null !== $index && isset($data[$index])) { + return $data[$index]; + } } } + return $data; } diff --git a/lib/internal/Magento/Framework/Model/AbstractModel.php b/lib/internal/Magento/Framework/Model/AbstractModel.php index dbbb20021b9f4..67db6a10c40db 100644 --- a/lib/internal/Magento/Framework/Model/AbstractModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractModel.php @@ -1,6 +1,6 @@ _beforeLoad($modelId, $field); $this->_getResource()->load($this, $modelId, $field); - $this->_afterLoad(); - $this->setOrigData(); - $this->_hasDataChanges = false; - $this->updateStoredData(); return $this; } @@ -577,6 +572,18 @@ protected function _afterLoad() return $this; } + /** + * Process operation before object load + * + * @param string $identifier + * @param string|null $field + * @return void + */ + public function beforeLoad($identifier, $field = null) + { + $this->_beforeLoad($identifier, $field); + } + /** * Object after load processing. Implemented as public interface for supporting objects after load in collections * @@ -584,7 +591,6 @@ protected function _afterLoad() */ public function afterLoad() { - $this->getResource()->afterLoad($this); $this->_afterLoad(); $this->updateStoredData(); return $this; diff --git a/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction.php b/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction.php index 2e207fde345e7..bbc9abe22efc1 100644 --- a/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction.php +++ b/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction.php @@ -2,7 +2,7 @@ /** * Action validator, remove action * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction/Allowed.php b/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction/Allowed.php index a956f99217e6e..dee1df394e2ea 100644 --- a/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction/Allowed.php +++ b/lib/internal/Magento/Framework/Model/ActionValidator/RemoveAction/Allowed.php @@ -2,7 +2,7 @@ /** * Action validator for remove action * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Model\ActionValidator\RemoveAction; diff --git a/lib/internal/Magento/Framework/Model/CallbackPool.php b/lib/internal/Magento/Framework/Model/CallbackPool.php index cf4fee33e276b..12b3e05927ac7 100644 --- a/lib/internal/Magento/Framework/Model/CallbackPool.php +++ b/lib/internal/Magento/Framework/Model/CallbackPool.php @@ -1,6 +1,6 @@ unsetData($field); } else { - $object->setData($field, serialize($value ?: $defaultValue)); + $object->setData($field, $this->getSerializer()->serialize($value ?: $defaultValue)); } return $this; @@ -132,13 +139,7 @@ protected function _serializeField(DataObject $object, $field, $defaultValue = n */ protected function _unserializeField(DataObject $object, $field, $defaultValue = null) { - $value = $object->getData($field); - - if ($value) { - $unserializedValue = @unserialize($value); - $value = $unserializedValue !== false || $value === 'b:0;' ? $unserializedValue : $value; - } - + $value = $this->getSerializer()->unserialize($object->getData($field)); if (empty($value)) { $object->setData($field, $defaultValue); } else { @@ -228,4 +229,18 @@ protected function _getColumnsForEntityLoad(\Magento\Framework\Model\AbstractMod } return $columns; } + + /** + * Get serializer + * + * @return Json + * @deprecated + */ + protected function getSerializer() + { + if (null === $this->serializer) { + $this->serializer = ObjectManager::getInstance()->get(Json::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index fc189473505ab..22ddc6bee445d 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -1,18 +1,20 @@ transactionManager = $context->getTransactionManager(); $this->_resources = $context->getResources(); $this->objectRelationProcessor = $context->getObjectRelationProcessor(); @@ -332,6 +336,7 @@ public function getConnection() */ public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null) { + $object->beforeLoad($value, $field); if ($field === null) { $field = $this->getIdFieldName(); } @@ -348,7 +353,10 @@ public function load(\Magento\Framework\Model\AbstractModel $object, $value, $fi $this->unserializeFields($object); $this->_afterLoad($object); - + $object->afterLoad(); + $object->setOrigData(); + $object->setHasDataChanges(false); + return $this; } @@ -375,6 +383,7 @@ protected function _getLoadSelect($field, $value, $object) * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @throws \Exception + * @throws AlreadyExistsException * @api */ public function save(\Magento\Framework\Model\AbstractModel $object) @@ -409,6 +418,10 @@ public function save(\Magento\Framework\Model\AbstractModel $object) } $this->addCommitCallback([$object, 'afterCommitCallback'])->commit(); $object->setHasDataChanges(false); + } catch (DuplicateException $e) { + $this->rollBack(); + $object->setHasDataChanges(true); + throw new AlreadyExistsException(new Phrase('Unique constraint violation found'), $e); } catch (\Exception $e) { $this->rollBack(); $object->setHasDataChanges(true); diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php index 6c82dafb6c44e..79cd8d4be318c 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php @@ -1,6 +1,6 @@ _serializeField($dataObject, $field, $defaultValue, $unsetEmpty); + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->serializerMock = $this->getMock(Json::class); + $this->abstractResource = $objectManager->getObject(AbstractResourceStub::class); + $objectManager->setBackwardCompatibleProperty( + $this->abstractResource, + 'serializer', + $this->serializerMock + ); + } - static::assertEquals($expectation, $dataObject->getDataByKey($field)); + /** + * @param array $arguments + * @param string $expected + * @param array|string|int $serializeCalledWith + * @param int $numSerializeCalled + * @dataProvider serializeFieldsDataProvider + */ + public function testSerializeFields( + array $arguments, + $expected, + $serializeCalledWith, + $numSerializeCalled = 1 + ) { + /** @var DataObject $dataObject */ + list($dataObject, $field, $defaultValue, $unsetEmpty) = $arguments; + $this->serializerMock->expects($this->exactly($numSerializeCalled)) + ->method('serialize') + ->with($serializeCalledWith) + ->willReturn($expected); + $this->abstractResource->_serializeField($dataObject, $field, $defaultValue, $unsetEmpty); + $this->assertEquals($expected, $dataObject->getData($field)); } /** * @return array */ - public function serializableFieldsDataProvider() + public function serializeFieldsDataProvider() { + $array = ['a', 'b', 'c']; + $string = 'i am string'; + $integer = 969; + $empty = ''; $dataObject = new DataObject( [ - 'object' => new \stdClass(), - 'array' => ['a', 'b', 'c'], - 'string' => 'i am string', - 'int' => 969, - 'serialized_object' => 'O:8:"stdClass":0:{}', - 'empty_value' => '', - 'empty_value_with_default' => '' + 'array' => $array, + 'string' => $string, + 'integer' => $integer, + 'empty' => $empty, + 'empty_with_default' => '' ] ); - return [ - [[$dataObject, 'object', null, false], serialize($dataObject->getDataByKey('object'))], - [[$dataObject, 'array', null, false], serialize($dataObject->getDataByKey('array'))], - [[$dataObject, 'string', null, false], serialize($dataObject->getDataByKey('string'))], - [[$dataObject, 'int', null, false], serialize($dataObject->getDataByKey('int'))], [ - [$dataObject, 'serialized_object', null, false], - serialize($dataObject->getDataByKey('serialized_object')) + [$dataObject, 'array', null, false], + '["a","b","c"]', + $array + ], + [ + [$dataObject, 'string', null, false], + '"i am string"', + $string + ], + [ + [$dataObject, 'integer', null, false], + '969', + $integer + ], + [ + [$dataObject, 'empty', null, true], + null, + $empty, + 0 ], - [[$dataObject, 'empty_value', null, true], null], - [[$dataObject, 'empty_value_with_default', new \stdClass(), false], 'O:8:"stdClass":0:{}'], + [ + [$dataObject, 'empty_with_default', 'default', false], + '"default"', + 'default' + ] ]; } /** * @param array $arguments - * @param mixed $expectation - * @dataProvider unserializableFieldsDataProvider + * @param array|string|int|boolean $expected + * @dataProvider unserializeFieldsDataProvider */ - public function testUnserializeFields(array $arguments, $expectation) + public function testUnserializeFields(array $arguments, $expected) { /** @var DataObject $dataObject */ list($dataObject, $field, $defaultValue) = $arguments; - - $abstractResource = new AbstractResourceStub(); - - $abstractResource->_unserializeField($dataObject, $field, $defaultValue); - - static::assertEquals($expectation, $dataObject->getDataByKey($field)); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with($dataObject->getData($field)) + ->willReturn($expected); + $this->abstractResource->_unserializeField($dataObject, $field, $defaultValue); + $this->assertEquals($expected, $dataObject->getData($field)); } /** * @return array */ - public function unserializableFieldsDataProvider() + public function unserializeFieldsDataProvider() { $dataObject = new DataObject( [ - 'object' => serialize(new \stdClass()), - 'array' => serialize(['a', 'b', 'c']), - 'string' => serialize('i am string'), - 'int' => serialize(969), - 'serialized_object' => serialize('O:8:"stdClass":0:{}'), - 'empty_value_with_default' => serialize(''), + 'array' => '["a","b","c"]', + 'string' => '"i am string"', + 'integer' => '969', + 'empty_with_default' => '""', 'not_serialized_string' => 'i am string', - 'serialized_boolean_false' => serialize(false) + 'serialized_boolean_false' => 'false' ] ); - - $defaultValue = new \stdClass(); - return [ - [[$dataObject, 'object', null], unserialize($dataObject->getDataByKey('object'))], - [[$dataObject, 'array', null], unserialize($dataObject->getDataByKey('array'))], - [[$dataObject, 'string', null], unserialize($dataObject->getDataByKey('string'))], - [[$dataObject, 'int', null], unserialize($dataObject->getDataByKey('int'))], - [[$dataObject, 'serialized_object', null], unserialize($dataObject->getDataByKey('serialized_object'))], - [[$dataObject, 'empty_value_with_default', $defaultValue], $defaultValue], - [[$dataObject, 'not_serialized_string', null], 'i am string'], - [[$dataObject, 'serialized_boolean_false', null], false] + [ + [$dataObject, 'array', null], + ['a', 'b', 'c'] + ], + [ + [$dataObject, 'string', null], + 'i am string' + ], + [ + [$dataObject, 'integer', null], + 969 + ], + [ + [$dataObject, 'empty_with_default', 'default', false], + 'default' + ], + [ + [$dataObject, 'not_serialized_string', null], + 'i am string' + ], + [ + [$dataObject, 'serialized_boolean_false', null], + false, + ] ]; } @@ -116,32 +174,31 @@ public function testCommitZeroLevel() ->disableOriginalConstructor() ->getMock(); - $abstractResource = new AbstractResourceStub(); - $abstractResource->setConnection($connection); - $abstractResource->addCommitCallback( + $this->abstractResource->setConnection($connection); + $this->abstractResource->addCommitCallback( function () use ($closureExpectation) { $closureExpectation->setData(1); } ); - $abstractResource->addCommitCallback( + $this->abstractResource->addCommitCallback( function () use ($closureExpectation) { $closureExpectation->getData(); } ); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('commit'); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('getTransactionLevel') ->willReturn(0); - $closureExpectation->expects(static::once()) + $closureExpectation->expects($this->once()) ->method('setData') ->with(1); - $closureExpectation->expects(static::once()) + $closureExpectation->expects($this->once()) ->method('getData'); - $abstractResource->commit(); + $this->abstractResource->commit(); } /** @@ -152,23 +209,22 @@ public function testCommitZeroLevelCallbackException() /** @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject $connection */ $connection = $this->getMock(AdapterInterface::class); - $abstractResource = new AbstractResourceStub(); - $abstractResource->setConnection($connection); - $abstractResource->addCommitCallback( + $this->abstractResource->setConnection($connection); + $this->abstractResource->addCommitCallback( function () { throw new \Exception(); } ); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('commit'); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('getTransactionLevel') ->willReturn(0); - $abstractResource->commit(); + $this->abstractResource->commit(); } - + public function testCommitNotCompletedTransaction() { /** @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject $connection */ @@ -178,24 +234,23 @@ public function testCommitNotCompletedTransaction() ->disableOriginalConstructor() ->getMock(); - $abstractResource = new AbstractResourceStub(); - $abstractResource->setConnection($connection); - $abstractResource->addCommitCallback( + $this->abstractResource->setConnection($connection); + $this->abstractResource->addCommitCallback( function () use ($closureExpectation) { $closureExpectation->setData(1); } ); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('commit'); - $connection->expects(static::once()) + $connection->expects($this->once()) ->method('getTransactionLevel') ->willReturn(1); - $closureExpectation->expects(static::never()) + $closureExpectation->expects($this->never()) ->method('setData') ->with(1); - $abstractResource->commit(); + $this->abstractResource->commit(); } } diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index 1dcb84d276840..a6f3add797d16 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -1,12 +1,16 @@ willReturn($this->transactionManagerMock); $this->_model = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [$contextMock], '', true, @@ -116,7 +120,7 @@ public function addUniqueFieldDataProvider() public function testAddUniqueFieldArray() { $this->assertInstanceOf( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, $this->_model->addUniqueField(['someField']) ); } @@ -134,7 +138,7 @@ public function testGetIdFieldname() { $data = 'MainTableName'; $idFieldNameProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, '_idFieldName' + AbstractDb::class, '_idFieldName' ); $idFieldNameProperty->setAccessible(true); $idFieldNameProperty->setValue($this->_model, $data); @@ -158,7 +162,7 @@ public function testGetMainTableException() public function testGetMainTable($tableName, $expectedResult) { $mainTableProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableProperty->setAccessible(true); @@ -195,7 +199,7 @@ public function testGetTable() $this->returnValue('tableName') ); $tablesProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_tables' ); $tablesProperty->setAccessible(true); @@ -215,7 +219,7 @@ public function testGetChecksumNegative() */ public function testGetChecksum($checksum, $expected) { - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class, [], [], '', false); + $connectionMock = $this->getMock(AdapterInterface::class, [], [], '', false); $connectionMock->expects($this->once())->method('getTablesChecksum')->with($checksum)->will( $this->returnValue([$checksum => 'checksum']) ); @@ -242,7 +246,7 @@ public function getChecksumProvider() public function testResetUniqueField() { $uniqueFields = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_uniqueFields' ); $uniqueFields->setAccessible(true); @@ -254,7 +258,7 @@ public function testResetUniqueField() public function testGetUniqueFields() { $uniqueFieldsReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_uniqueFields' ); $uniqueFieldsReflection->setAccessible(true); @@ -269,36 +273,26 @@ public function testGetValidationRulesBeforeSave() public function testLoad() { - $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); - $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); - $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, - [$contextMock, $registryMock], - '', - false, - true, - true, - ['__wakeup'] - ); - - $value = 'some_value'; - $idFieldName = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - '_idFieldName' - ); - $idFieldName->setAccessible(true); - $idFieldName->setValue($this->_model, 'field_value'); - + /** @var \Magento\Framework\Model\AbstractModel|\PHPUnit_Framework_MockObject_MockObject $object */ + $object = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + ->disableOriginalConstructor() + ->getMock(); + $object->expects($this->once())->method('beforeLoad')->with('some_value', 'field_name'); + $object->expects($this->once())->method('afterLoad')->willReturnSelf(); + $object->expects($this->once())->method('setOrigData')->willReturnSelf(); + $object->expects($this->once())->method('setHasDataChanges')->with(false)->willReturnSelf(); + $result = $this->_model->load($object, 'some_value', 'field_name'); + $this->assertEquals($this->_model, $result); $this->assertInstanceOf( \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - $this->_model->load($abstractModelMock, $value, $idFieldName) + $result ); } public function testDelete() { $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -307,7 +301,7 @@ public function testDelete() $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -321,7 +315,7 @@ public function testDelete() ); $abstractModelMock->expects($this->once())->method('getData')->willReturn(['data' => 'value']); - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $connectionMock = $this->getMock(AdapterInterface::class); $this->transactionManagerMock->expects($this->once()) ->method('start') ->with($connectionInterfaceMock) @@ -344,13 +338,13 @@ public function testDelete() $this->returnValue('tableName') ); $mainTableReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableReflection->setAccessible(true); $mainTableReflection->setValue($this->_model, 'tableName'); $idFieldNameReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_idFieldName' ); $idFieldNameReflection->setAccessible(true); @@ -361,7 +355,7 @@ public function testDelete() $abstractModelMock->expects($this->once())->method('afterDelete'); $abstractModelMock->expects($this->once())->method('afterDeleteCommit'); $this->assertInstanceOf( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, $this->_model->delete($abstractModelMock) ); } @@ -371,7 +365,7 @@ public function testHasDataChangedNegative() $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -391,7 +385,7 @@ public function testHasDataChangedNegative() public function testGetDataChanged($getOriginData, $expected) { $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -403,7 +397,7 @@ public function testGetDataChanged($getOriginData, $expected) $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -412,7 +406,7 @@ public function testGetDataChanged($getOriginData, $expected) ['__wakeup', 'getOrigData', 'getData'] ); $mainTableProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableProperty->setAccessible(true); @@ -441,13 +435,13 @@ public function hasDataChangedDataProvider() public function testPrepareDataForUpdate() { - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class, [], [], '', false); + $connectionMock = $this->getMock(AdapterInterface::class, [], [], '', false); $context = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( \Magento\Framework\Model\Context::class ); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $resourceMock = $this->getMock( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [ '_construct', 'getConnection', @@ -459,7 +453,7 @@ public function testPrepareDataForUpdate() false ); $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -472,7 +466,7 @@ public function testPrepareDataForUpdate() ->disableOriginalConstructor() ->getMockForAbstractClass(); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$context, $registryMock, $resourceMock, $resourceCollectionMock] ); $data = 'tableName'; @@ -484,20 +478,19 @@ public function testPrepareDataForUpdate() $this->returnValue('tableName') ); $mainTableReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableReflection->setAccessible(true); $mainTableReflection->setValue($this->_model, 'tableName'); $idFieldNameReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_idFieldName' ); $idFieldNameReflection->setAccessible(true); $idFieldNameReflection->setValue($this->_model, 'idFieldName'); $connectionMock->expects($this->any())->method('save')->with('tableName', 'idFieldName'); $connectionMock->expects($this->any())->method('quoteInto')->will($this->returnValue('idFieldName')); - $abstractModelMock->setIdFieldName('id'); $abstractModelMock->setData( [ @@ -551,7 +544,7 @@ public function testSaveNewObject($pkIncrement) /** * Mock SUT so as not to test extraneous logic */ - $model = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class) + $model = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['_prepareDataForSave', 'getIdFieldName', 'getConnection', 'getMainTable']) ->getMockForAbstractClass(); @@ -568,7 +561,7 @@ public function testSaveNewObject($pkIncrement) $reflectionProperty->setValue($model, $pkIncrement); // Mocked behavior - $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + $connectionMock = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->setMethods(['lastInsertId']) ->getMockForAbstractClass(); @@ -590,7 +583,7 @@ public function testSaveNewObject($pkIncrement) // Only set object id if not PK autoincrement $setIdInvokedCount = $pkIncrement ? 1 : 0; - $inputObject = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + $inputObject = $this->getMockBuilder(AbstractModel::class) ->disableOriginalConstructor() ->getMock(); $inputObject->expects($this->exactly($setIdInvokedCount))->method('setId'); @@ -602,9 +595,37 @@ public function testSaveNewObject($pkIncrement) $reflectionMethod->invokeArgs($model, [$inputObject]); } + /** + * @return array + */ public function saveNewObjectDataProvider() { return [[true], [false]]; } + /** + * @expectedException \Magento\Framework\Exception\AlreadyExistsException + */ + public function testDuplicateExceptionProcessingOnSave() + { + $connection = $this->getMock(AdapterInterface::class); + $connection->expects($this->once())->method('rollback'); + + /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $model */ + $model = $this->getMockBuilder(AbstractDb::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection']) + ->getMockForAbstractClass(); + $model->expects($this->any())->method('getConnection')->willReturn($connection); + + /** @var AbstractModel|\PHPUnit_Framework_MockObject_MockObject $object */ + $object = $this->getMockBuilder(AbstractModel::class) + ->disableOriginalConstructor() + ->getMock(); + $object->expects($this->once())->method('hasDataChanges')->willReturn(true); + $object->expects($this->once())->method('beforeSave')->willThrowException(new DuplicateException()); + $object->expects($this->once())->method('setHasDataChanges')->with(true); + + $model->save($object); + } } diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php index d458810732b6b..51fbd2ba00290 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php @@ -1,6 +1,6 @@ isEnabled('Vendor_Module'); + * ``` */ -namespace Magento\Framework\Module; - class Manager { /** - * @var Output\ConfigInterface + * The checker of output modules. + * + * @var Output\ConfigInterface the config checker of output modules. + * @deprecated Magento does not support custom disabling/enabling module output since 2.2.0 version. + * The property can be removed in a future major release */ - private $_outputConfig; + private $outputConfig; /** - * @var ModuleListInterface + * The list of all modules. + * + * @var ModuleListInterface the list of all modules. */ - private $_moduleList; + private $moduleList; /** - * @var array + * The list of config paths to ignore. + * + * @var array the list of config paths to ignore. + * @deprecated Magento does not support custom disabling/enabling module output since 2.2.0 version. + * The property can be removed in a future major release */ - private $_outputConfigPaths; + private $outputConfigPaths; /** - * @param Output\ConfigInterface $outputConfig - * @param ModuleListInterface $moduleList - * @param array $outputConfigPaths + * Constructor. + * + * @param Output\ConfigInterface $outputConfig the checker of output modules + * @param ModuleListInterface $moduleList the list of all modules + * @param array $outputConfigPaths the list of config paths to ignore */ public function __construct( Output\ConfigInterface $outputConfig, ModuleListInterface $moduleList, array $outputConfigPaths = [] ) { - $this->_outputConfig = $outputConfig; - $this->_moduleList = $moduleList; - $this->_outputConfigPaths = $outputConfigPaths; + $this->outputConfig = $outputConfig; + $this->moduleList = $moduleList; + $this->outputConfigPaths = $outputConfigPaths; } /** - * Whether a module is enabled in the configuration or not + * Checks whether a module is enabled in the configuration or not. * - * @param string $moduleName Fully-qualified module name - * @return boolean + * @param string $moduleName the fully-qualified module name + * + * @return boolean true if module is enabled, false otherwise */ public function isEnabled($moduleName) { - return $this->_moduleList->has($moduleName); + return $this->moduleList->has($moduleName); } /** - * Whether a module output is permitted by the configuration or not + * Checks whether a module output is permitted by the configuration or not. + * + * @param string $moduleName the fully-qualified module name. * - * @param string $moduleName Fully-qualified module name * @return boolean + * @deprecated Magento does not support custom disabling/enabling module output since 2.2.0 version */ public function isOutputEnabled($moduleName) { - if (!$this->isEnabled($moduleName)) { - return false; - } - if (!$this->_isCustomOutputConfigEnabled($moduleName)) { - return false; - } - if ($this->_outputConfig->isEnabled($moduleName)) { - return false; - } - return true; + return $this->isEnabled($moduleName); } /** - * Whether a configuration switch for a module output permits output or not + * Checks whether a configuration switch for a module output permits output. * * @param string $moduleName Fully-qualified module name + * * @return boolean + * @deprecated Magento does not support custom disabling/enabling module output since 2.2.0 version. + * The method can be removed in a future major release + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _isCustomOutputConfigEnabled($moduleName) { - if (isset($this->_outputConfigPaths[$moduleName])) { - $configPath = $this->_outputConfigPaths[$moduleName]; - if (defined($configPath)) { - $configPath = constant($configPath); - } - return $this->_outputConfig->isSetFlag($configPath); - } return true; } } diff --git a/lib/internal/Magento/Framework/Module/ModuleList.php b/lib/internal/Magento/Framework/Module/ModuleList.php index caaa5aba3153e..3c1605bed2eb7 100644 --- a/lib/internal/Magento/Framework/Module/ModuleList.php +++ b/lib/internal/Magento/Framework/Module/ModuleList.php @@ -1,6 +1,6 @@ isSetFlag(sprintf(self::XML_PATH_MODULE_OUTPUT_STATUS, $moduleName)); + return false; } /** - * @inheritdoc + * Retrieve module enabled specific path + * + * @param string $path Fully-qualified config path + * @deprecated Magento does not support custom disabling/enabling module output since 2.2.0 version + * @return boolean */ public function isSetFlag($path) { - return $this->_scopeConfig->isSetFlag($path, $this->_storeType); + return false; } } diff --git a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php index 2794c424fbe8a..ef55778dfe17c 100644 --- a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php +++ b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php @@ -1,16 +1,22 @@ diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/DependencyCheckerTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/DependencyCheckerTest.php index 2c199d5bcf797..301541f4d0f0b 100644 --- a/lib/internal/Magento/Framework/Module/Test/Unit/DependencyCheckerTest.php +++ b/lib/internal/Magento/Framework/Module/Test/Unit/DependencyCheckerTest.php @@ -1,6 +1,6 @@ _moduleList = $this->getMockForAbstractClass(\Magento\Framework\Module\ModuleListInterface::class); - $this->_moduleList->expects($this->any()) - ->method('getOne') - ->will($this->returnValueMap([ - ['Module_One', ['name' => 'One_Module', 'setup_version' => '1']], - ['Module_Two', ['name' => 'Two_Module', 'setup_version' => '2']], - ['Module_Three', ['name' => 'Two_Three']], - ])); - $this->_outputConfig = $this->getMockForAbstractClass(\Magento\Framework\Module\Output\ConfigInterface::class); - $this->_model = new \Magento\Framework\Module\Manager( - $this->_outputConfig, - $this->_moduleList, - [ - 'Module_Two' => self::XML_PATH_OUTPUT_ENABLED, - ] - ); - } - - public function testIsEnabled() - { - $this->_moduleList->expects($this->exactly(2))->method('has')->will($this->returnValueMap([ - ['Module_Exists', true], - ['Module_NotExists', false], - ])); - $this->assertTrue($this->_model->isEnabled('Module_Exists')); - $this->assertFalse($this->_model->isEnabled('Module_NotExists')); - } - - public function testIsOutputEnabledReturnsFalseForDisabledModule() - { - $this->_outputConfig->expects($this->any())->method('isSetFlag')->will($this->returnValue(true)); - $this->assertFalse($this->_model->isOutputEnabled('Disabled_Module')); - } + private $outputConfig; /** - * @param bool $configValue - * @param bool $expectedResult - * @dataProvider isOutputEnabledGenericConfigPathDataProvider + * @inheritdoc */ - public function testIsOutputEnabledGenericConfigPath($configValue, $expectedResult) + protected function setUp() { - $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true)); - $this->_outputConfig->expects($this->once()) - ->method('isEnabled') - ->with('Module_One') - ->will($this->returnValue($configValue)); - $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_One')); - } + $this->moduleList = $this->getMockBuilder(ModuleListInterface::class) + ->getMockForAbstractClass(); + $this->outputConfig = $this->getMockBuilder(ConfigInterface::class) + ->getMockForAbstractClass(); - public function isOutputEnabledGenericConfigPathDataProvider() - { - return ['output disabled' => [true, false], 'output enabled' => [false, true]]; + $this->model = new Manager( + $this->outputConfig, + $this->moduleList + ); } - /** - * @param bool $configValue - * @param bool $expectedResult - * @dataProvider isOutputEnabledCustomConfigPathDataProvider - */ - public function testIsOutputEnabledCustomConfigPath($configValue, $expectedResult) + public function testIsEnabled() { - $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true)); - $this->_outputConfig->expects($this->at(0)) - ->method('isSetFlag') - ->with(self::XML_PATH_OUTPUT_ENABLED) - ->will($this->returnValue($configValue)); - $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_Two')); - } + $this->moduleList->expects($this->exactly(2)) + ->method('has') + ->willReturnMap( + [ + ['Module_Exists', true], + ['Module_NotExists', false], + ] + ); - public function isOutputEnabledCustomConfigPathDataProvider() - { - return [ - 'path literal, output disabled' => [false, false], - 'path literal, output enabled' => [true, true], - ]; + $this->assertTrue($this->model->isEnabled('Module_Exists')); + $this->assertFalse($this->model->isEnabled('Module_NotExists')); } } diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php index 5c5a64f53a086..d2a51746067ff 100644 --- a/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php +++ b/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/Mview/ActionFactory.php b/lib/internal/Magento/Framework/Mview/ActionFactory.php index 77b0c89c8cc39..af65ca15cb424 100644 --- a/lib/internal/Magento/Framework/Mview/ActionFactory.php +++ b/lib/internal/Magento/Framework/Mview/ActionFactory.php @@ -1,6 +1,6 @@ stateCollection = $stateCollection; $isCacheExists = $cache->test($cacheId); - parent::__construct($reader, $cache, $cacheId); + parent::__construct($reader, $cache, $cacheId, $serializer); if (!$isCacheExists) { $this->deleteNonexistentStates(); diff --git a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php index 694d88e63ae47..c2e4c75b433fa 100644 --- a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php +++ b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php @@ -1,6 +1,6 @@ [], 'view3' => []]; + private $views = ['view1' => [], 'view3' => []]; + + /** + * @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializerMock; protected function setUp() { @@ -58,28 +63,29 @@ protected function setUp() true, ['getItems'] ); + + $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\SerializerInterface::class); } public function testConstructorWithCache() { $this->cache->expects($this->once())->method('test')->with($this->cacheId)->will($this->returnValue(true)); - $this->cache->expects( - $this->once() - )->method( - 'load' - )->with( - $this->cacheId - )->will( - $this->returnValue(serialize($this->views)) - ); + $this->cache->expects($this->once()) + ->method('load') + ->with($this->cacheId); $this->stateCollection->expects($this->never())->method('getItems'); - $this->model = new \Magento\Framework\Mview\Config\Data( + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturn($this->views); + + $this->config = new \Magento\Framework\Mview\Config\Data( $this->reader, $this->cache, $this->stateCollection, - $this->cacheId + $this->cacheId, + $this->serializerMock ); } @@ -114,11 +120,12 @@ public function testConstructorWithoutCache() $this->stateCollection->expects($this->once())->method('getItems')->will($this->returnValue($states)); - $this->model = new \Magento\Framework\Mview\Config\Data( + $this->config = new \Magento\Framework\Mview\Config\Data( $this->reader, $this->cache, $this->stateCollection, - $this->cacheId + $this->cacheId, + $this->serializerMock ); } } diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php index a0f8e377862d0..cb81c9932d366 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php @@ -1,6 +1,6 @@ connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, [], [], '', false); - $this->resourceMock = $this->getMock( - \Magento\Framework\App\ResourceConnection::class, - [], - [], - '', - false, - false + $this->resourceMock = $this->getMock( + \Magento\Framework\App\ResourceConnection::class, + [], + [], + '', + false, + false ); $this->connectionMock->expects($this->any()) @@ -57,19 +57,19 @@ protected function setUp() ->method('getConnection') ->willReturn($this->connectionMock); - $this->triggerFactoryMock = $this->getMock( + $this->triggerFactoryMock = $this->getMock( \Magento\Framework\DB\Ddl\TriggerFactory::class, [], [], '', false, false ); - $this->viewCollectionMock = $this->getMockForAbstractClass( + $this->viewCollectionMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\CollectionInterface::class, [], '', false, false, true, [] ); - $this->viewMock = $this->getMockForAbstractClass( + $this->viewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $this->resourceMock->expects($this->any()) ->method('getTableName') - ->willReturn($this->tableName); + ->will($this->returnArgument(0)); $this->model = new Subscription( $this->resourceMock, @@ -96,11 +96,15 @@ public function testGetColumnName() $this->assertEquals('columnName', $this->model->getColumnName()); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testCreate() { $triggerName = 'trigger_name'; $this->resourceMock->expects($this->atLeastOnce())->method('getTriggerName')->willReturn($triggerName); $triggerMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Trigger::class) + ->setMethods(['setName', 'getName', 'setTime', 'setEvent', 'setTable', 'addStatement']) ->disableOriginalConstructor() ->getMock(); $triggerMock->expects($this->exactly(3)) @@ -121,11 +125,38 @@ public function testCreate() ->method('setTable') ->with($this->tableName) ->will($this->returnSelf()); - $triggerMock->expects($this->exactly(6)) + + $triggerMock->expects($this->at(4)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(5)) + ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(11)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(12)) + ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(18)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (OLD.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(19)) ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (OLD.columnName);") ->will($this->returnSelf()); - $changelogMock = $this->getMockForAbstractClass( + $changelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $changelogMock->expects($this->exactly(3)) @@ -143,7 +174,7 @@ public function testCreate() ->method('create') ->will($this->returnValue($triggerMock)); - $otherChangelogMock = $this->getMockForAbstractClass( + $otherChangelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $otherChangelogMock->expects($this->exactly(3)) @@ -153,7 +184,7 @@ public function testCreate() ->method('getColumnName') ->will($this->returnValue('entity_id')); - $otherViewMock = $this->getMockForAbstractClass( + $otherViewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $otherViewMock->expects($this->exactly(1)) @@ -216,7 +247,7 @@ public function testRemove() ->method('create') ->will($this->returnValue($triggerMock)); - $otherChangelogMock = $this->getMockForAbstractClass( + $otherChangelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $otherChangelogMock->expects($this->exactly(3)) @@ -226,7 +257,7 @@ public function testRemove() ->method('getColumnName') ->will($this->returnValue('entity_id')); - $otherViewMock = $this->getMockForAbstractClass( + $otherViewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $otherViewMock->expects($this->exactly(1)) diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/ViewTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/ViewTest.php index cf17286af0e9e..5371b8470bc66 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/ViewTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/ViewTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_merged_two.xml b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_merged_two.xml index 32d049f1570cf..030dc1b7c16fc 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_merged_two.xml +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_merged_two.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_one.xml b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_one.xml index 1ec8400e7f303..f6da086def77b 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_one.xml +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_one.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_three.xml b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_three.xml index a0ee312109cd4..340a12f306d1d 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_three.xml +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_three.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_two.xml b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_two.xml index 7e67bee354883..1553d12eaadb0 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_two.xml +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/mview_two.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/valid_mview.xml b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/valid_mview.xml index d53d88943faf5..c4bba92f9955b 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/_files/valid_mview.xml +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/_files/valid_mview.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Mview/View.php b/lib/internal/Magento/Framework/Mview/View.php index 16d88767e508c..80b743b3f87fa 100644 --- a/lib/internal/Magento/Framework/Mview/View.php +++ b/lib/internal/Magento/Framework/Mview/View.php @@ -1,6 +1,6 @@ @@ -106,7 +106,7 @@ - Subscription model must be a valid PHP class or interface name. + DEPRECATED. Subscription model must be a valid PHP class or interface name. diff --git a/lib/internal/Magento/Framework/Notification/MessageInterface.php b/lib/internal/Magento/Framework/Notification/MessageInterface.php index cb5392b1114fe..70cf1637e412c 100644 --- a/lib/internal/Magento/Framework/Notification/MessageInterface.php +++ b/lib/internal/Magento/Framework/Notification/MessageInterface.php @@ -2,7 +2,7 @@ /** * System message * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/lib/internal/Magento/Framework/Notification/MessageList.php b/lib/internal/Magento/Framework/Notification/MessageList.php index 87e0905f89229..dcaa6e04618b8 100644 --- a/lib/internal/Magento/Framework/Notification/MessageList.php +++ b/lib/internal/Magento/Framework/Notification/MessageList.php @@ -1,6 +1,6 @@ arguments[$type])) { + if (array_key_exists($type, $this->arguments)) { if (is_string($this->arguments[$type])) { - $this->arguments[$type] = unserialize($this->arguments[$type]); + $this->arguments[$type] = $this->getSerializer()->unserialize($this->arguments[$type]); + } else if ($this->arguments[$type] === null) { + $this->arguments[$type] = []; } return $this->arguments[$type]; } else { - return [['_i_' => \Magento\Framework\ObjectManagerInterface::class]]; + return null; } } @@ -129,9 +143,15 @@ public function getPreference($type) */ public function extend(array $configuration) { - $this->arguments = $configuration['arguments']; - $this->virtualTypes = $configuration['instanceTypes']; - $this->preferences = $configuration['preferences']; + $this->arguments = isset($configuration['arguments']) + ? array_replace($this->arguments, $configuration['arguments']) + : $this->arguments; + $this->virtualTypes = isset($configuration['instanceTypes']) + ? array_replace($this->virtualTypes, $configuration['instanceTypes']) + : $this->virtualTypes; + $this->preferences = isset($configuration['preferences']) + ? array_replace($this->preferences, $configuration['preferences']) + : $this->preferences; } /** @@ -153,4 +173,19 @@ public function getPreferences() { return $this->preferences; } + + /** + * Get serializer + * + * @return SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if (null === $this->serializer) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(Serialize::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Config/Config.php b/lib/internal/Magento/Framework/ObjectManager/Config/Config.php index 5dc02e6ba7d97..e94699730cdcd 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Config/Config.php +++ b/lib/internal/Magento/Framework/ObjectManager/Config/Config.php @@ -1,10 +1,11 @@ _cache) { if (!$this->_currentCacheKey) { $this->_currentCacheKey = md5( - serialize([$this->_arguments, $this->_nonShared, $this->_preferences, $this->_virtualTypes]) + $this->getSerializer()->serialize( + [$this->_arguments, $this->_nonShared, $this->_preferences, $this->_virtualTypes] + ) ); } - $key = md5($this->_currentCacheKey . serialize($configuration)); + $key = md5($this->_currentCacheKey . $this->getSerializer()->serialize($configuration)); $cached = $this->_cache->get($key); if ($cached) { list( @@ -323,4 +331,19 @@ public function getPreferences() { return $this->_preferences; } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Config/Mapper/ArgumentParser.php b/lib/internal/Magento/Framework/ObjectManager/Config/Mapper/ArgumentParser.php index dc7463c0b1735..dfd2aa596b211 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Config/Mapper/ArgumentParser.php +++ b/lib/internal/Magento/Framework/ObjectManager/Config/Mapper/ArgumentParser.php @@ -1,6 +1,6 @@ _signatures, $this->_definitions) = $definitions; - $this->reader = $reader ?: new \Magento\Framework\Code\Reader\ClassReader(); - } - - /** - * Unpack signature - * - * @param string $signature - * @return mixed - */ - abstract protected function _unpack($signature); - - /** - * Get list of method parameters - * - * Retrieve an ordered list of constructor parameters. - * Each value is an array with following entries: - * - * array( - * 0, // string: Parameter name - * 1, // string|null: Parameter type - * 2, // bool: whether this param is required - * 3, // mixed: default value - * ); - * - * @param string $className - * @return array|null - */ - public function getParameters($className) - { - // if the definition isn't found in the list gathered from the compiled file then using reflection to find it - if (!array_key_exists($className, $this->_definitions)) { - return $this->reader->getConstructor($className); - } - - $definition = $this->_definitions[$className]; - if ($definition !== null) { - if (is_string($this->_signatures[$definition])) { - $this->_signatures[$definition] = $this->_unpack($this->_signatures[$definition]); - } - return $this->_signatures[$definition]; - } - return null; - } - - /** - * Retrieve list of all classes covered with definitions - * - * @return array - */ - public function getClasses() - { - return array_keys($this->_definitions); - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Definition/Compiled/Binary.php b/lib/internal/Magento/Framework/ObjectManager/Definition/Compiled/Binary.php deleted file mode 100644 index bba816e072e6b..0000000000000 --- a/lib/internal/Magento/Framework/ObjectManager/Definition/Compiled/Binary.php +++ /dev/null @@ -1,27 +0,0 @@ - \Magento\Framework\ObjectManager\Definition\Compiled\Binary::class, - Serialized::MODE_NAME => \Magento\Framework\ObjectManager\Definition\Compiled\Serialized::class, - ]; - /** * @var \Magento\Framework\Code\Generator */ @@ -74,39 +38,26 @@ class DefinitionFactory /** * @param DriverInterface $filesystemDriver - * @param string $definitionDir * @param string $generationDir - * @param string $definitionFormat */ - public function __construct(DriverInterface $filesystemDriver, $definitionDir, $generationDir, $definitionFormat) - { + public function __construct( + DriverInterface $filesystemDriver, + $generationDir + ) { $this->_filesystemDriver = $filesystemDriver; - $this->_definitionDir = $definitionDir; $this->_generationDir = $generationDir; - $this->_definitionFormat = $definitionFormat; } /** * Create class definitions * - * @param mixed $definitions - * @return Runtime + * @return DefinitionInterface */ - public function createClassDefinition($definitions = false) + public function createClassDefinition() { - if ($definitions) { - if (is_string($definitions)) { - $definitions = $this->_unpack($definitions); - } - $definitionModel = self::$definitionClasses[$this->_definitionFormat]; - $result = new $definitionModel($definitions); - } else { - $autoloader = new \Magento\Framework\Code\Generator\Autoloader($this->getCodeGenerator()); - spl_autoload_register([$autoloader, 'load']); - - $result = new Runtime(); - } - return $result; + $autoloader = new Autoloader($this->getCodeGenerator()); + spl_autoload_register([$autoloader, 'load']); + return new Runtime(); } /** @@ -116,14 +67,7 @@ public function createClassDefinition($definitions = false) */ public function createPluginDefinition() { - $path = $this->_definitionDir . '/plugins.ser'; - if ($this->_filesystemDriver->isReadable($path)) { - return new \Magento\Framework\Interception\Definition\Compiled( - $this->_unpack($this->_filesystemDriver->fileGetContents($path)) - ); - } else { - return new \Magento\Framework\Interception\Definition\Runtime(); - } + return new \Magento\Framework\Interception\Definition\Runtime(); } /** @@ -133,36 +77,7 @@ public function createPluginDefinition() */ public function createRelations() { - $path = $this->_definitionDir . '/' . 'relations.ser'; - if ($this->_filesystemDriver->isReadable($path)) { - return new \Magento\Framework\ObjectManager\Relations\Compiled( - $this->_unpack($this->_filesystemDriver->fileGetContents($path)) - ); - } else { - return new \Magento\Framework\ObjectManager\Relations\Runtime(); - } - } - - /** - * Gets supported definition formats - * - * @return array - */ - public static function getSupportedFormats() - { - return array_keys(self::$definitionClasses); - } - - /** - * Un-compress definitions - * - * @param string $definitions - * @return mixed - */ - protected function _unpack($definitions) - { - $extractor = $this->_definitionFormat == Binary::MODE_NAME ? 'igbinary_unserialize' : 'unserialize'; - return $extractor($definitions); + return new \Magento\Framework\ObjectManager\Relations\Runtime(); } /** diff --git a/lib/internal/Magento/Framework/ObjectManager/DefinitionInterface.php b/lib/internal/Magento/Framework/ObjectManager/DefinitionInterface.php index f330c101b749f..9cd2cb1dec8a4 100644 --- a/lib/internal/Magento/Framework/ObjectManager/DefinitionInterface.php +++ b/lib/internal/Magento/Framework/ObjectManager/DefinitionInterface.php @@ -2,7 +2,7 @@ /** * Object Manager class definition interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\ObjectManager; diff --git a/lib/internal/Magento/Framework/ObjectManager/DynamicConfigInterface.php b/lib/internal/Magento/Framework/ObjectManager/DynamicConfigInterface.php index 319572c6fd8a9..dd399c33ccb1f 100644 --- a/lib/internal/Magento/Framework/ObjectManager/DynamicConfigInterface.php +++ b/lib/internal/Magento/Framework/ObjectManager/DynamicConfigInterface.php @@ -1,6 +1,6 @@ config = $config; $this->objectManager = $objectManager; - $this->definitions = $definitions ?: new \Magento\Framework\ObjectManager\Definition\Runtime(); + $this->definitions = $definitions ?: $this->getDefinitions(); $this->globalArguments = $globalArguments; } @@ -79,6 +86,17 @@ public function setArguments($arguments) $this->globalArguments = $arguments; } + /** + * @return \Magento\Framework\ObjectManager\DefinitionInterface + */ + public function getDefinitions() + { + if ($this->definitions === null) { + $this->definitions = new \Magento\Framework\ObjectManager\Definition\Runtime(); + } + return $this->definitions; + } + /** * Create object * @@ -177,4 +195,44 @@ protected function parseArray(&$array) } } } + + /** + * Resolve constructor arguments + * + * @param string $requestedType + * @param array $parameters + * @param array $arguments + * + * @return array + * + * @throws \UnexpectedValueException + * @throws \BadMethodCallException + */ + protected function resolveArgumentsInRuntime($requestedType, array $parameters, array $arguments = []) + { + $resolvedArguments = []; + foreach ($parameters as $parameter) { + list($paramName, $paramType, $paramRequired, $paramDefault) = $parameter; + $argument = null; + if (!empty($arguments) && (isset($arguments[$paramName]) || array_key_exists($paramName, $arguments))) { + $argument = $arguments[$paramName]; + } elseif ($paramRequired) { + if ($paramType) { + $argument = ['instance' => $paramType]; + } else { + $this->creationStack = []; + throw new \BadMethodCallException( + 'Missing required argument $' . $paramName . ' of ' . $requestedType . '.' + ); + } + } else { + $argument = $paramDefault; + } + + $this->resolveArgument($argument, $paramType, $paramDefault, $paramName, $requestedType); + + $resolvedArguments[] = $argument; + } + return $resolvedArguments; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Factory/Compiled.php b/lib/internal/Magento/Framework/ObjectManager/Factory/Compiled.php index 84b63bd74c745..845a2e60c5494 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Factory/Compiled.php +++ b/lib/internal/Magento/Framework/ObjectManager/Factory/Compiled.php @@ -1,7 +1,6 @@ config->getArguments($requestedType); $type = $this->config->getInstanceType($requestedType); - if (!$args) { + if ($args === []) { + // Case 1: no arguments required return new $type(); - } - - foreach ($args as $key => &$argument) { - if (isset($arguments[$key])) { - $argument = $arguments[$key]; - } elseif (isset($argument['_i_'])) { - $argument = $this->get($argument['_i_']); - } elseif (isset($argument['_ins_'])) { - $argument = $this->create($argument['_ins_']); - } elseif (isset($argument['_v_'])) { - $argument = $argument['_v_']; - } elseif (isset($argument['_vac_'])) { - $argument = $argument['_vac_']; - $this->parseArray($argument); - } elseif (isset($argument['_vn_'])) { - $argument = null; - } elseif (isset($argument['_a_'])) { - if (isset($this->globalArguments[$argument['_a_']])) { - $argument = $this->globalArguments[$argument['_a_']]; - } else { - $argument = $argument['_d_']; + } else if ($args !== null) { + /** + * Case 2: arguments retrieved from pre-compiled DI cache + * + * Argument key meanings: + * + * _i_: shared instance of a class or interface + * _ins_: non-shared instance of a class or interface + * _v_: non-array literal value + * _vac_: array, may be nested and contain other types of keys listed here (objects, array, nulls, etc) + * _vn_: null value + * _a_: value to be taken from named environment variable + * _d_: default value in case environment variable specified by _a_ does not exist + */ + foreach ($args as $key => &$argument) { + if (isset($arguments[$key])) { + $argument = $arguments[$key]; + } elseif (isset($argument['_i_'])) { + $argument = $this->get($argument['_i_']); + } elseif (isset($argument['_ins_'])) { + $argument = $this->create($argument['_ins_']); + } elseif (isset($argument['_v_'])) { + $argument = $argument['_v_']; + } elseif (isset($argument['_vac_'])) { + $argument = $argument['_vac_']; + $this->parseArray($argument); + } elseif (isset($argument['_vn_'])) { + $argument = null; + } elseif (isset($argument['_a_'])) { + if (isset($this->globalArguments[$argument['_a_']])) { + $argument = $this->globalArguments[$argument['_a_']]; + } else { + $argument = $argument['_d_']; + } } } + $args = array_values($args); + } else { + // Case 3: arguments retrieved in runtime + $parameters = $this->getDefinitions()->getParameters($type) ?: []; + $args = $this->resolveArgumentsInRuntime( + $type, + $parameters, + $arguments + ); } - $args = array_values($args); - return $this->createObject($type, $args); } diff --git a/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Developer.php b/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Developer.php index 909df40762de4..5d4600e604fb0 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Developer.php +++ b/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Developer.php @@ -1,19 +1,12 @@ config->getArguments($requestedType), $arguments) - : $this->config->getArguments($requestedType); - foreach ($parameters as $parameter) { - list($paramName, $paramType, $paramRequired, $paramDefault) = $parameter; - $argument = null; - if (!empty($arguments) && (isset($arguments[$paramName]) || array_key_exists($paramName, $arguments))) { - $argument = $arguments[$paramName]; - } elseif ($paramRequired) { - if ($paramType) { - $argument = ['instance' => $paramType]; - } else { - $this->creationStack = []; - throw new \BadMethodCallException( - 'Missing required argument $' . $paramName . ' of ' . $requestedType . '.' - ); - } + // Get default arguments from config, merge with supplied arguments + $defaultArguments = $this->config->getArguments($requestedType); + if (is_array($defaultArguments)) { + if (count($arguments)) { + $arguments = array_replace($defaultArguments, $arguments); } else { - $argument = $paramDefault; + $arguments = $defaultArguments; } - - $this->resolveArgument($argument, $paramType, $paramDefault, $paramName, $requestedType); - - $resolvedArguments[] = $argument; } - return $resolvedArguments; + + return $this->resolveArgumentsInRuntime($requestedType, $parameters, $arguments); } /** diff --git a/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Production.php b/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Production.php index daafcee74f873..9814ed1283928 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Production.php +++ b/lib/internal/Magento/Framework/ObjectManager/Factory/Dynamic/Production.php @@ -1,6 +1,6 @@ _relations = $relations; - } - - /** - * Check whether requested type is available for read - * - * @param string $type - * @return bool - */ - public function has($type) - { - return isset($this->_relations[$type]); - } - - /** - * Retrieve parents for class - * - * @param string $type - * @return array - */ - public function getParents($type) - { - return $this->_relations[$type]; - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Relations/Runtime.php b/lib/internal/Magento/Framework/ObjectManager/Relations/Runtime.php index 7e6ac27814541..92d72b14c120d 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Relations/Runtime.php +++ b/lib/internal/Magento/Framework/ObjectManager/Relations/Runtime.php @@ -1,6 +1,6 @@ objectManager = new ObjectManager($this); + $this->serializerMock = $this->getMock(SerializerInterface::class); + + $initialData = [ + 'arguments' => [ + 'type1' => 'initial serialized configuration for type1', + 'class_with_no_arguments_serialized' => null, + 'class_with_arguments_serialized' => 'serialized arguments', + 'class_with_arguments_unserialized' => ['unserialized', 'arguments'], + 'class_with_no_arguments_unserialized' => [], + ], + 'instanceTypes' => [ + 'instanceType1' => 'instanceTypeValue1', + 'instanceType2' => 'instanceTypeValue2' + ], + 'preferences' => [ + 'preference1' => 'preferenceValue1', + 'preference2' => 'preferenceValue2' + ] + ]; + + $this->compiled = $this->objectManager->getObject( + Compiled::class, + [ + 'data' => $initialData, + 'serializer' => $this->serializerMock + ] + ); + } + + public function testExtend() + { + + $configuration = [ + 'arguments' => [ + 'type1' => 'serialized configuration for type1', + 'type2' => 'serialized configuration for type2' + ], + 'instanceTypes' => [ + 'instanceType2' => 'newInstanceTypeValue2', + 'instanceType3' => 'newInstanceTypeValue3' + ], + 'preferences' => [ + 'preference1' => 'newPreferenceValue1' + ] + ]; + $expectedArguments = [ + 'type1' => [ + 'argument1_1' => 'newArgumentValue1_1' + ], + 'type2' => [ + 'argument2_1' => 'newArgumentValue2_1' + ] + ]; + $expectedVirtualTypes = [ + 'instanceType1' => 'instanceTypeValue1', + 'instanceType2' => 'newInstanceTypeValue2', + 'instanceType3' => 'newInstanceTypeValue3' + ]; + $expectedPreferences = [ + 'preference1' => 'newPreferenceValue1', + 'preference2' => 'preferenceValue2' + ]; + + $this->serializerMock->expects($this->at(0)) + ->method('unserialize') + ->with($configuration['arguments']['type1']) + ->willReturn($expectedArguments['type1']); + $this->serializerMock->expects($this->at(1)) + ->method('unserialize') + ->with($configuration['arguments']['type2']) + ->willReturn($expectedArguments['type2']); + + $this->compiled->extend($configuration); + foreach ($expectedArguments as $type => $arguments) { + $this->assertEquals($arguments, $this->compiled->getArguments($type)); + } + $this->assertEquals($expectedVirtualTypes, $this->compiled->getVirtualTypes()); + $this->assertEquals($expectedPreferences, $this->compiled->getPreferences()); + } + + /** + * Arguments defined in array, have not previously been unserialized + */ + public function testGetArgumentsSerialized() + { + $unserializedArguments = ['unserialized', 'arguments']; + + // method called twice but after one unserialization, unserialized version should be stored + $this->serializerMock->expects($this->once())->method('unserialize') + ->with('serialized arguments') + ->willReturn($unserializedArguments); + + $this->assertSame($unserializedArguments, $this->compiled->getArguments('class_with_arguments_serialized')); + $this->assertSame($unserializedArguments, $this->compiled->getArguments('class_with_arguments_serialized')); + } + + /** + * Arguments defined in array, have not previously been unserialized + */ + public function testGetArgumentsSerializedEmpty() + { + $this->serializerMock->expects($this->never())->method('unserialize'); + $this->assertSame([], $this->compiled->getArguments('class_with_no_arguments_serialized')); + } + + /** + * Arguments defined in array, have previously been unserialized + */ + public function testGetArgumentsUnserialized() + { + $unserializedArguments = ['unserialized', 'arguments']; + $this->serializerMock->expects($this->never())->method('unserialize'); + $this->assertSame($unserializedArguments, $this->compiled->getArguments('class_with_arguments_unserialized')); + } + + /** + * Arguments are defined but empty + */ + public function testGetArgumentsUnserializedEmpty() + { + $this->serializerMock->expects($this->never())->method('unserialize'); + $this->assertSame([], $this->compiled->getArguments('class_with_no_arguments_unserialized')); + } + + /** + * Arguments not defined in array + */ + public function testGetArgumentsNotDefined() + { + $this->serializerMock->expects($this->never())->method('unserialize'); + $this->assertSame(null, $this->compiled->getArguments('class_not_stored_in_config')); + } +} diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/ConfigTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/ConfigTest.php index 844d5fa94a627..29561991f28d0 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/ConfigTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/ConfigTest.php @@ -1,14 +1,23 @@ objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + } + public function testGetArgumentsEmpty() { $config = new Config(); @@ -42,6 +51,14 @@ public function testExtendWithCacheMock() $cache->expects($this->once())->method('get')->will($this->returnValue(false)); $config = new Config(null, $definitions); + $serializerMock = $this->getMock(SerializerInterface::class); + $serializerMock->expects($this->exactly(2)) + ->method('serialize'); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $config, + 'serializer', + $serializerMock + ); $config->setCache($cache); $this->_assertFooTypeArguments($config); diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/ArgumentParserTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/ArgumentParserTest.php index d3907cfece779..c38a8e11f7c78 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/ArgumentParserTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/ArgumentParserTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/_files/mapped_simple_di_config.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/_files/mapped_simple_di_config.php index dd94048d59d84..eb20fa307f718 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/_files/mapped_simple_di_config.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Mapper/_files/mapped_simple_di_config.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Reader/DomFactoryTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Reader/DomFactoryTest.php index 2f5c6027d0f1f..1ec8c28e20390 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Reader/DomFactoryTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/Reader/DomFactoryTest.php @@ -1,6 +1,6 @@ [ + ' + + + + + Some_Class_Name + + + + ', + [ + "Element 'item', attribute 'sortOrder': 'false' is not a valid value of the atomic type 'xs:integer'." . + "\nLine: 6\n" + ], + ], ]; diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/_files/valid_config.xml b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/_files/valid_config.xml index df900f0ce4a9c..852a3b3163339 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/_files/valid_config.xml +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Config/_files/valid_config.xml @@ -1,7 +1,7 @@ @@ -57,6 +57,11 @@ some_value + + Instance_test_name_two + Instance_test_name_one + Instance_test_name_three + diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/BinaryTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/BinaryTest.php deleted file mode 100644 index 80cf73a44e7ac..0000000000000 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/BinaryTest.php +++ /dev/null @@ -1,21 +0,0 @@ -markTestSkipped('This test requires igbinary PHP extension'); - } - $checkString = 'packed code'; - $signatures = ['wonderfulClass' => igbinary_serialize($checkString)]; - $definitions = ['wonderful' => 'wonderfulClass']; - $model = new \Magento\Framework\ObjectManager\Definition\Compiled\Binary([$signatures, $definitions]); - $this->assertEquals($checkString, $model->getParameters('wonderful')); - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/SerializedTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/SerializedTest.php deleted file mode 100644 index 9454377c3b720..0000000000000 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/Compiled/SerializedTest.php +++ /dev/null @@ -1,35 +0,0 @@ - null]; - $model = new \Magento\Framework\ObjectManager\Definition\Compiled\Serialized([$signatures, $definitions]); - $this->assertEquals(null, $model->getParameters('wonderful')); - } - - public function testGetParametersWithSignatureObject() - { - $wonderfulSignature = new \stdClass(); - $signatures = ['wonderfulClass' => $wonderfulSignature]; - $definitions = ['wonderful' => 'wonderfulClass']; - $model = new \Magento\Framework\ObjectManager\Definition\Compiled\Serialized([$signatures, $definitions]); - $this->assertEquals($wonderfulSignature, $model->getParameters('wonderful')); - } - - public function testGetParametersWithUnpacking() - { - $checkString = 'code to pack'; - $signatures = ['wonderfulClass' => serialize($checkString)]; - $definitions = ['wonderful' => 'wonderfulClass']; - $model = new \Magento\Framework\ObjectManager\Definition\Compiled\Serialized([$signatures, $definitions]); - $this->assertEquals($checkString, $model->getParameters('wonderful')); - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/CompiledStub.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/CompiledStub.php deleted file mode 100644 index 16b8436a0c41a..0000000000000 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Definition/CompiledStub.php +++ /dev/null @@ -1,25 +0,0 @@ -getMock( - \Magento\Framework\Code\Reader\ClassReader::class, - ['getConstructor'], - [], - '', - false - ); - $readerMock->expects($this->once()) - ->method('getConstructor') - ->with($className) - ->willReturn($undefinedDefinitionSignature); - $model = $objectManager->getObject( - \Magento\Framework\ObjectManager\Test\Unit\Definition\CompiledStub::class, - [ - 'definitions' => [[], []], - 'reader' => $readerMock - ] - ); - $this->assertEquals($undefinedDefinitionSignature, $model->getParameters($className)); - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/DefinitionFactoryTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/DefinitionFactoryTest.php index 468e01ce80aba..9d61b925e2141 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/DefinitionFactoryTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/DefinitionFactoryTest.php @@ -1,121 +1,58 @@ sampleContent = serialize([1, 2, 3]); - $this->filesystemDriverMock = $this->getMock( - \Magento\Framework\Filesystem\Driver\File::class, - [], - [], - '', - false - ); - $this->model = new \Magento\Framework\ObjectManager\DefinitionFactory( + $this->filesystemDriverMock = $this->getMock(File::class); + $this->definitionFactory = new DefinitionFactory( $this->filesystemDriverMock, - 'DefinitionDir', - 'GenerationDir', - Serialized::MODE_NAME + 'generation dir' ); } - public function testCreateClassDefinitionFromString() + public function testCreateClassDefinition() { $this->assertInstanceOf( - \Magento\Framework\ObjectManager\Definition\Compiled\Serialized::class, - $this->model->createClassDefinition($this->sampleContent) + DefinitionInterface::class, + $this->definitionFactory->createClassDefinition() ); } - /** - * @param string $path - * @param string $callMethod - * @param string $expectedClass - * @dataProvider createPluginsAndRelationsReadableDataProvider - */ - public function testCreatePluginsAndRelationsReadable($path, $callMethod, $expectedClass) - { - $this->filesystemDriverMock->expects($this->once())->method('isReadable') - ->with($path) - ->will($this->returnValue(true)); - $this->filesystemDriverMock->expects($this->once())->method('fileGetContents') - ->with($path) - ->will($this->returnValue($this->sampleContent)); - $this->assertInstanceOf($expectedClass, $this->model->$callMethod()); - } - - public function createPluginsAndRelationsReadableDataProvider() - { - return [ - 'relations' => [ - 'DefinitionDir/relations.ser', - 'createRelations', \Magento\Framework\ObjectManager\Relations\Compiled::class, - ], - 'plugins' => [ - 'DefinitionDir/plugins.ser', - 'createPluginDefinition', \Magento\Framework\Interception\Definition\Compiled::class, - ], - ]; - } - - /** - * @param string $path - * @param string $callMethod - * @param string $expectedClass - * @dataProvider createPluginsAndRelationsNotReadableDataProvider - */ - public function testCreatePluginsAndRelationsNotReadable($path, $callMethod, $expectedClass) + public function testCreatePluginDefinition() { - $this->filesystemDriverMock->expects($this->once())->method('isReadable') - ->with($path) - ->will($this->returnValue(false)); - $this->assertInstanceOf($expectedClass, $this->model->$callMethod()); - } - - public function createPluginsAndRelationsNotReadableDataProvider() - { - return [ - 'relations' => [ - 'DefinitionDir/relations.ser', - 'createRelations', \Magento\Framework\ObjectManager\Relations\Runtime::class, - ], - 'plugins' => [ - 'DefinitionDir/plugins.ser', - 'createPluginDefinition', \Magento\Framework\Interception\Definition\Runtime::class, - ], - ]; + $this->assertInstanceOf( + InterceptionDefinitionInterface::class, + $this->definitionFactory->createPluginDefinition() + ); } - public function testGetSupportedFormats() + public function testCreateRelations() { - $actual = \Magento\Framework\ObjectManager\DefinitionFactory::getSupportedFormats(); - $this->assertInternalType('array', $actual); - foreach ($actual as $className) { - $this->assertInternalType('string', $className); - } + $this->assertInstanceOf( + RelationsInterface::class, + $this->definitionFactory->createRelations() + ); } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php index dbf7da6bb5eab..6cd28c5177c10 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php @@ -1,52 +1,60 @@ objectManager = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + $this->objectManager = new ObjectManager($this); + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->setMethods([]) ->getMock(); - $this->config = $this->getMockBuilder(\Magento\Framework\ObjectManager\ConfigInterface::class) + $this->config = $this->getMockBuilder(ConfigInterface::class) ->setMethods([]) ->getMock(); $this->sharedInstances = []; $this->factory = new Compiled($this->config, $this->sharedInstances, []); - $this->factory->setObjectManager($this->objectManager); + $this->factory->setObjectManager($this->objectManagerMock); + + $this->definitionsMock = $this->getMockBuilder(DefinitionInterface::class)->getMock(); + $this->objectManager->setBackwardCompatibleProperty($this->factory, 'definitions', $this->definitionsMock); } public function testCreateSimple() @@ -54,10 +62,9 @@ public function testCreateSimple() $expectedConfig = $this->getSimpleConfig(); $requestedType = 'requestedType'; - $type = \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting::class; - $sharedType = - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencySharedTesting::class; - $nonSharedType = \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencyTesting::class; + $type = SimpleClassTesting::class; + $sharedType = DependencySharedTesting::class; + $nonSharedType = DependencyTesting::class; $this->config->expects($this->any()) ->method('getArguments') @@ -84,11 +91,11 @@ public function testCreateSimple() ] ); - /** @var \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting $result */ + /** @var SimpleClassTesting $result */ $result = $this->factory->create($requestedType, []); $this->assertInstanceOf( - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting::class, + SimpleClassTesting::class, $result ); $this->assertInstanceOf($sharedType, $result->getSharedDependency()); @@ -103,11 +110,11 @@ public function testCreateSimpleConfiguredArguments() { $expectedConfig = $this->getSimpleNestedConfig(); - $type = \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting::class; + $type = SimpleClassTesting::class; $requestedType = 'requestedType'; $sharedType = - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencySharedTesting::class; - $nonSharedType = \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencyTesting::class; + DependencySharedTesting::class; + $nonSharedType = DependencyTesting::class; $this->config->expects($this->any()) ->method('getArguments') @@ -135,11 +142,11 @@ public function testCreateSimpleConfiguredArguments() ] ); - /** @var \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting $result */ + /** @var SimpleClassTesting $result */ $result = $this->factory->create($requestedType, []); $this->assertInstanceOf( - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting::class, + SimpleClassTesting::class, $result ); $this->assertInstanceOf($sharedType, $result->getSharedDependency()); @@ -163,8 +170,45 @@ public function testCreateSimpleConfiguredArguments() $this->assertNull($result->getNullValue()); } + public function testCreateGetArgumentsInRuntime() + { + // Stub OM to create test assets + $this->config->expects($this->any())->method('isShared')->willReturn(true); + $this->objectManagerMock->expects($this->any())->method('get')->willReturnMap( + [ + [DependencyTesting::class, new DependencyTesting()], + [DependencySharedTesting::class, new DependencySharedTesting()] + ] + ); + + // Simulate case where compiled DI config not found + $type = SimpleClassTesting::class; + $this->config->expects($this->any())->method('getArguments')->willReturn(null); + $this->config->expects($this->any())->method('getInstanceType')->willReturnArgument(0); + $this->definitionsMock->expects($this->once()) + ->method('getParameters') + ->with($type) + ->willReturn($this->getRuntimeParameters()); + + $sharedType = DependencySharedTesting::class; + $nonSharedType = DependencyTesting::class; + + // Run SUT + /** @var SimpleClassTesting $result */ + $result = $this->factory->create($type, []); + + $this->assertInstanceOf($type, $result); + $this->assertInstanceOf($sharedType, $result->getSharedDependency()); + $this->assertInstanceOf($nonSharedType, $result->getNonSharedDependency()); + $this->assertEquals('value', $result->getValue()); + $this->assertEquals(['default_value1', 'default_value2'], $result->getValueArray()); + $this->assertEquals(null, $result->getGlobalValue()); + $this->assertNull($result->getNullValue()); + } + /** - * Returns simple config + * Returns simple config with default constructor values for + * \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting * * @return array */ @@ -172,11 +216,11 @@ private function getSimpleConfig() { return [ 'nonSharedDependency' => [ - '_ins_' => \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencyTesting::class, + '_ins_' => DependencyTesting::class, ], 'sharedDependency' => [ '_i_' => - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencySharedTesting::class, + DependencySharedTesting::class, ], 'value' => [ '_v_' => 'value', @@ -195,7 +239,8 @@ private function getSimpleConfig() } /** - * Returns nested config + * Returns config for \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting + * with non-default nested array value for the $value_array parameter * * @return array */ @@ -203,11 +248,11 @@ private function getSimpleNestedConfig() { return [ 'nonSharedDependency' => [ - '_ins_' => \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencyTesting::class, + '_ins_' => DependencyTesting::class, ], 'sharedDependency' => [ '_i_' => - \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\DependencySharedTesting::class, + DependencySharedTesting::class, ], 'value' => [ '_v_' => 'value', @@ -249,4 +294,63 @@ private function getSimpleNestedConfig() ] ]; } + + /** + * Returns mock parameter list for + * \Magento\Framework\ObjectManager\Test\Unit\Factory\Fixture\Compiled\SimpleClassTesting + * as would be found by \Magento\Framework\ObjectManager\DefinitionInterface + * + * @return array + */ + private function getRuntimeParameters() + { + return [ + 0 => + [ + 0 => 'nonSharedDependency', + 1 => DependencyTesting::class, + 2 => true, + 3 => null, + ], + 1 => + [ + 0 => 'sharedDependency', + 1 => DependencySharedTesting::class, + 2 => true, + 3 => null, + ], + 2 => + [ + 0 => 'value', + 1 => null, + 2 => false, + 3 => 'value', + ], + 3 => + [ + 0 => 'valueArray', + 1 => null, + 2 => false, + 3 => + [ + 0 => 'default_value1', + 1 => 'default_value2', + ], + ], + 4 => + [ + 0 => 'globalValue', + 1 => null, + 2 => false, + 3 => '', + ], + 5 => + [ + 0 => 'nullValue', + 1 => null, + 2 => false, + 3 => null, + ], + ]; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/FactoryTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/FactoryTest.php index 91fbe465862f1..bfeae6cedfe9b 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/FactoryTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/FactoryTest.php @@ -1,6 +1,6 @@ 'yes']; - - $model = new \Magento\Framework\ObjectManager\Relations\Compiled($relations); - $this->assertEquals(true, $model->has('amazing')); - $this->assertEquals(false, $model->has('fuzzy')); - } - - public function testGetParents() - { - $relations = ['amazing' => 'parents']; - - $model = new \Magento\Framework\ObjectManager\Relations\Compiled($relations); - $this->assertEquals('parents', $model->getParents('amazing')); - } -} diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Relations/RuntimeTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Relations/RuntimeTest.php index cb274f845b668..6c66dde1e80cd 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Relations/RuntimeTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Relations/RuntimeTest.php @@ -1,6 +1,6 @@ @@ -20,6 +20,7 @@ + diff --git a/lib/internal/Magento/Framework/ObjectManagerInterface.php b/lib/internal/Magento/Framework/ObjectManagerInterface.php index e30b040866b74..6041f63d1e7d4 100644 --- a/lib/internal/Magento/Framework/ObjectManagerInterface.php +++ b/lib/internal/Magento/Framework/ObjectManagerInterface.php @@ -1,6 +1,6 @@ with($this->equalTo($totalAmount), $this->equalTo($expectedAdjustments)) ->will($this->returnValue($amountBaseMock)); - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $productMock = $this->getMockBuilder(\Magento\Framework\Pricing\SaleableInterface::class) ->disableOriginalConstructor() ->setMethods(['getPriceInfo', '__wakeup']) - ->getMock(); + ->getMockForAbstractClass(); - $weeeAdjustmentMock = $this->getMockBuilder(\Magento\Weee\Pricing\Adjustment::class) + $weeeAdjustmentMock = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\AdjustmentInterface::class) ->disableOriginalConstructor() ->getMock(); $weeeAdjustmentMock->expects($this->once()) @@ -82,7 +82,7 @@ public function testGetAmount() ->with($this->equalTo($amountInclTax), $this->equalTo($productMock)) ->will($this->returnValue($weeeAdjustment + $amountInclTax)); - $taxAdjustmentMock = $this->getMockBuilder(\Magento\Tax\Pricing\Adjustment::class) + $taxAdjustmentMock = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\AdjustmentInterface::class) ->disableOriginalConstructor() ->getMock(); $taxAdjustmentMock->expects($this->once()) @@ -127,12 +127,12 @@ public function testGetAmountExclude() $adjustment = 5; $expectedAdjustments = []; - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $productMock = $this->getMockBuilder(\Magento\Framework\Pricing\SaleableInterface::class) ->disableOriginalConstructor() ->setMethods(['getPriceInfo', '__wakeup']) - ->getMock(); + ->getMockForAbstractClass(); - $taxAdjustmentMock = $this->getMockBuilder(\Magento\Tax\Pricing\Adjustment::class) + $taxAdjustmentMock = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\AdjustmentInterface::class) ->disableOriginalConstructor() ->getMock(); $taxAdjustmentMock->expects($this->once()) @@ -150,7 +150,7 @@ public function testGetAmountExclude() ->with($this->equalTo($fullamount), $this->equalTo($productMock)) ->will($this->returnValue($amount)); - $weeeAdjustmentMock = $this->getMockBuilder(\Magento\Weee\Pricing\Adjustment::class) + $weeeAdjustmentMock = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\AdjustmentInterface::class) ->disableOriginalConstructor() ->getMock(); $weeeAdjustmentMock->expects($this->once()) diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Adjustment/CollectionTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Adjustment/CollectionTest.php index a156104b57388..ade0b98b835be 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Adjustment/CollectionTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Adjustment/CollectionTest.php @@ -1,6 +1,6 @@ assertEquals($this->rendererPool, $this->model->getRendererPool()); } + + /** + * This tests ensures that protected method getCacheLifetime() returns a null value when cacheLifeTime is not + * explicitly set in the parent block + */ + public function testCacheLifetime() + { + $reflectionClass = new \ReflectionClass(get_class($this->model)); + $methodReflection = $reflectionClass->getMethod('getCacheLifetime'); + $methodReflection->setAccessible(true); + $cacheLifeTime = $methodReflection->invoke($this->model); + $this->assertNull($cacheLifeTime, 'Expected null cache lifetime'); + } } diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/RendererPoolTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/RendererPoolTest.php index 7d400b6933c54..5d16612d61045 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/RendererPoolTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/RendererPoolTest.php @@ -1,6 +1,6 @@ disableOriginalConstructor() ->getMock(); - $this->price = $this->getMockBuilder(\Magento\Catalog\Pricing\Price\BasePrice::class) + $this->price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $this->amount = $this->getMockBuilder(\Magento\Framework\Pricing\Amount\Base::class) ->disableOriginalConstructor() ->getMock(); - $this->saleableItem = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $this->saleableItem = $this->getMockBuilder(\Magento\Framework\Pricing\SaleableInterface::class) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $this->renderPool = $this->getMockBuilder(\Magento\Framework\Pricing\Render\RendererPool::class) ->disableOriginalConstructor() diff --git a/lib/internal/Magento/Framework/Process/PhpExecutableFinderFactory.php b/lib/internal/Magento/Framework/Process/PhpExecutableFinderFactory.php index 4780fd408d413..8328d021dc298 100644 --- a/lib/internal/Magento/Framework/Process/PhpExecutableFinderFactory.php +++ b/lib/internal/Magento/Framework/Process/PhpExecutableFinderFactory.php @@ -1,6 +1,6 @@ serviceInterfaceMethodsMap[$key])) { $methodMap = $this->cache->load($key); if ($methodMap) { - $this->serviceInterfaceMethodsMap[$key] = unserialize($methodMap); + $this->serviceInterfaceMethodsMap[$key] = $this->getSerializer()->unserialize($methodMap); } else { $methodMap = $this->getMethodMapViaReflection($interfaceName); $this->serviceInterfaceMethodsMap[$key] = $methodMap; - $this->cache->save(serialize($this->serviceInterfaceMethodsMap[$key]), $key); + $this->cache->save($this->getSerializer()->serialize($this->serviceInterfaceMethodsMap[$key]), $key); } } return $this->serviceInterfaceMethodsMap[$key]; @@ -117,7 +123,7 @@ public function getMethodParams($serviceClassName, $serviceMethodName) $cacheId = self::SERVICE_METHOD_PARAMS_CACHE_PREFIX . hash('md5', $serviceClassName . $serviceMethodName); $params = $this->cache->load($cacheId); if ($params !== false) { - return unserialize($params); + return $this->getSerializer()->unserialize($params); } $serviceClass = new ClassReflection($serviceClassName); /** @var MethodReflection $serviceMethod */ @@ -133,7 +139,7 @@ public function getMethodParams($serviceClassName, $serviceMethodName) self::METHOD_META_DEFAULT_VALUE => $isDefaultValueAvailable ? $paramReflection->getDefaultValue() : null ]; } - $this->cache->save(serialize($params), $cacheId, [ReflectionCache::CACHE_TAG]); + $this->cache->save($this->getSerializer()->serialize($params), $cacheId, [ReflectionCache::CACHE_TAG]); return $params; } @@ -217,4 +223,19 @@ public function isMethodReturnValueRequired($type, $methodName) $methods = $this->getMethodsMap($type); return $methods[$methodName]['isRequired']; } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/Reflection/NameFinder.php b/lib/internal/Magento/Framework/Reflection/NameFinder.php index 9de6125cf1d3d..4172d3f2715ae 100644 --- a/lib/internal/Magento/Framework/Reflection/NameFinder.php +++ b/lib/internal/Magento/Framework/Reflection/NameFinder.php @@ -1,6 +1,6 @@ getMockForAbstractClass(); $fieldNamerMock = $this->getMockBuilder(\Magento\Framework\Reflection\FieldNamer::class) ->getMockForAbstractClass(); - $this->model = $objectManager->getObject( + $this->object = $objectManager->getObject( \Magento\Framework\Reflection\MethodsMap::class, [ 'cache' => $cacheMock, @@ -48,27 +51,33 @@ protected function setUp() 'fieldNamer' => $fieldNamerMock, ] ); + $this->serializerMock = $this->getMock(SerializerInterface::class); + $objectManager->setBackwardCompatibleProperty( + $this->object, + 'serializer', + $this->serializerMock + ); } public function testGetMethodReturnType() { $this->assertEquals( 'string', - $this->model->getMethodReturnType( + $this->object->getMethodReturnType( \Magento\Framework\Reflection\FieldNamer::class, 'getFieldNameForMethodName' ) ); $this->assertEquals( 'mixed', - $this->model->getMethodReturnType( + $this->object->getMethodReturnType( \Magento\Framework\Reflection\TypeCaster::class, 'castValueToType' ) ); $this->assertEquals( 'array', - $this->model->getMethodReturnType( + $this->object->getMethodReturnType( \Magento\Framework\Reflection\MethodsMap::class, 'getMethodsMap' ) @@ -77,42 +86,46 @@ public function testGetMethodReturnType() public function testGetMethodsMap() { - $methodsMap = $this->model->getMethodsMap(\Magento\Framework\Reflection\MethodsMap::class); - $this->assertEquals( - [ - 'getMethodReturnType' => [ - 'type' => 'string', - 'isRequired' => true, - 'description' => null, - 'parameterCount' => 2, - ], - 'getMethodsMap' => [ - 'type' => 'array', - 'isRequired' => true, - 'description' => "
     Service methods' reflection data stored in cache as 'methodName' => "
    -                        . "'returnType' ex. [ 'create' => '\Magento\Customer\Api\Data\Customer', 'validatePassword' "
    -                        . "=> 'boolean' ] 
    ", - 'parameterCount' => 1, - ], - 'getMethodParams' => [ - 'type' => 'array', - 'isRequired' => true, - 'description' => null, - 'parameterCount' => 2 - ], - 'isMethodValidForDataField' => [ - 'type' => 'bool', - 'isRequired' => true, - 'description' => null, - 'parameterCount' => 2, - ], - 'isMethodReturnValueRequired' => [ - 'type' => 'bool', - 'isRequired' => true, - 'description' => null, - 'parameterCount' => 2, - ], + $data = [ + 'getMethodReturnType' => [ + 'type' => 'string', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + 'getMethodsMap' => [ + 'type' => 'array', + 'isRequired' => true, + 'description' => "
     Service methods' reflection data stored in cache as 'methodName' => "
    +                    . "'returnType' ex. [ 'create' => '\Magento\Customer\Api\Data\Customer', 'validatePassword' "
    +                    . "=> 'boolean' ] 
    ", + 'parameterCount' => 1, ], + 'getMethodParams' => [ + 'type' => 'array', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2 + ], + 'isMethodValidForDataField' => [ + 'type' => 'bool', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + 'isMethodReturnValueRequired' => [ + 'type' => 'bool', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + ]; + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->with($data); + $methodsMap = $this->object->getMethodsMap(\Magento\Framework\Reflection\MethodsMap::class); + $this->assertEquals( + $data, $methodsMap ); } @@ -125,7 +138,7 @@ public function testGetMethodsMap() */ public function testIsMethodValidForDataField($type, $methodName, $expectedResult) { - $this->assertEquals($this->model->isMethodValidForDataField($type, $methodName), $expectedResult); + $this->assertEquals($this->object->isMethodValidForDataField($type, $methodName), $expectedResult); } /** @@ -157,7 +170,7 @@ public function isMethodValidForDataFieldProvider() */ public function testIsMethodReturnValueRequired($type, $methodName, $expectedResult) { - $this->assertEquals($this->model->isMethodValidForDataField($type, $methodName), $expectedResult); + $this->assertEquals($this->object->isMethodValidForDataField($type, $methodName), $expectedResult); } /** diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php index 6fa186ee5802e..363031309e334 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php @@ -1,6 +1,6 @@ columns @@ -30,7 +30,7 @@ public function build(RequestBucketInterface $bucket) foreach ($metrics as $metric) { $metricType = $metric->getType(); - if (in_array($metricType, $this->mapMetrics)) { + if (in_array($metricType, $this->allowedMetrics, true)) { $selectAggregations[$metricType] = "$metricType(main_table.value)"; } } diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Range.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Range.php index 2188657bd89ae..9cce6e2dbfadc 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Range.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Range.php @@ -1,6 +1,6 @@ scoreBuilderFactory = $scoreBuilderFactory; $this->filterBuilder = $filterBuilder; @@ -102,6 +116,11 @@ public function __construct( $this->queryContainerFactory = $queryContainerFactory; $this->matchBuilder = $matchBuilder; $this->temporaryStorage = $temporaryStorageFactory->create(); + $this->temporaryStorageFactory = $temporaryStorageFactory; + if (!in_array($relevanceCalculationMethod, ['SUM', 'MAX'], true)) { + throw new \LogicException('Unsupported relevance calculation method used. Only SUM and MAX are allowed'); + } + $this->relevanceCalculationMethod = $relevanceCalculationMethod; } /** @@ -150,9 +169,12 @@ public function buildQuery(RequestInterface $request) } /** + * Creates Select which wraps search result select + * + * It is used to group search results by entity id. + * * @param Select $select * @param ScoreBuilder $scoreBuilder - * @param string $scorePattern * @return Select */ private function createAroundSelect(Select $select, ScoreBuilder $scoreBuilder) @@ -162,7 +184,7 @@ private function createAroundSelect(Select $select, ScoreBuilder $scoreBuilder) ['main_select' => $select], [ $this->entityMetadata->getEntityId() => 'entity_id', - 'relevance' => sprintf('MAX(%s)', $scoreBuilder->getScoreAlias()) + 'relevance' => sprintf('%s(%s)', $this->relevanceCalculationMethod, $scoreBuilder->getScoreAlias()), ] )->group($this->entityMetadata->getEntityId()); return $parentSelect; diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php index a30ff53f9a35a..5bee3b833cf7c 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php @@ -1,6 +1,6 @@ setMethods(['getQuery', 'getIndex', 'getSize']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->request->expects($this->exactly(2)) + $this->request->expects($this->any()) ->method('getIndex') ->will($this->returnValue(self::INDEX_NAME)); @@ -537,4 +537,20 @@ private function createSelectMock(Select $from = null, $isInternal = true, $isGr return $select; } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Unsupported relevance calculation method used. + */ + public function testUnsupportedRelevanceCalculationMethod() + { + $helper = new ObjectManager($this); + $helper->getObject( + \Magento\Framework\Search\Adapter\Mysql\Mapper::class, + [ + 'indexProviders' => [], + 'relevanceCalculationMethod' => 'UNSUPPORTED' + ] + ); + } } diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/Query/Builder/MatchTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/Query/Builder/MatchTest.php index c677e045f6377..2626d68428c34 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/Query/Builder/MatchTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/Query/Builder/MatchTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/Search/etc/requests.xsd b/lib/internal/Magento/Framework/Search/etc/requests.xsd index f185699c5a5e8..67ce202c7f951 100644 --- a/lib/internal/Magento/Framework/Search/etc/requests.xsd +++ b/lib/internal/Magento/Framework/Search/etc/requests.xsd @@ -1,7 +1,7 @@ @@ -263,6 +263,7 @@ + diff --git a/lib/internal/Magento/Framework/Search/etc/search_engine.xsd b/lib/internal/Magento/Framework/Search/etc/search_engine.xsd index c6dad2d6b3f8c..3ca174a8d49c5 100644 --- a/lib/internal/Magento/Framework/Search/etc/search_engine.xsd +++ b/lib/internal/Magento/Framework/Search/etc/search_engine.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Search/etc/search_request.xsd b/lib/internal/Magento/Framework/Search/etc/search_request.xsd index e4ca02d9f0997..01851b1695d5a 100644 --- a/lib/internal/Magento/Framework/Search/etc/search_request.xsd +++ b/lib/internal/Magento/Framework/Search/etc/search_request.xsd @@ -1,7 +1,7 @@ @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Search/etc/search_request_merged.xsd b/lib/internal/Magento/Framework/Search/etc/search_request_merged.xsd index dab110ee5333a..b0243ae7e5b4c 100644 --- a/lib/internal/Magento/Framework/Search/etc/search_request_merged.xsd +++ b/lib/internal/Magento/Framework/Search/etc/search_request_merged.xsd @@ -1,7 +1,7 @@ @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Serialize/README.md b/lib/internal/Magento/Framework/Serialize/README.md new file mode 100644 index 0000000000000..5af8fb7f71b6b --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/README.md @@ -0,0 +1,8 @@ +# Serialize + +**Serialize** library provides interface *SerializerInterface* and multiple implementations: + + * *Json* - default implementation. Uses PHP native json_encode/json_decode functions; + * *Serialize* - less secure than *Json*, but gives higher performance on big arrays. Uses PHP native serialize/unserialize functions, does not unserialize objects on PHP 7. + +Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities. \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/Json.php b/lib/internal/Magento/Framework/Serialize/Serializer/Json.php new file mode 100644 index 0000000000000..24fadfcb0deee --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/Json.php @@ -0,0 +1,30 @@ +getPhpVersion() >= 7) { + return unserialize($string, ['allowed_classes' => false]); + } + return unserialize($string); + } + + /** + * Return major PHP version + * + * @return int + */ + private function getPhpVersion() + { + return PHP_MAJOR_VERSION; + } +} diff --git a/lib/internal/Magento/Framework/Serialize/SerializerInterface.php b/lib/internal/Magento/Framework/Serialize/SerializerInterface.php new file mode 100644 index 0000000000000..3fc589fab7a8e --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/SerializerInterface.php @@ -0,0 +1,28 @@ +json = $objectManager->getObject(Json::class); + } + + /** + * @param string|int|float|bool|array|null $value + * @param string $expected + * @dataProvider serializeDataProvider + */ + public function testSerialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->serialize($value) + ); + } + + public function serializeDataProvider() + { + $dataObject = new DataObject(['something']); + return [ + ['', '""'], + ['string', '"string"'], + [null, 'null'], + [false, 'false'], + [['a' => 'b', 'd' => 123], '{"a":"b","d":123}'], + [123, '123'], + [10.56, '10.56'], + [$dataObject, '{}'], + ]; + } + + /** + * @param string $value + * @param string|int|float|bool|array|null $expected + * @dataProvider unserializeDataProvider + */ + public function testUnserialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->unserialize($value) + ); + } + + public function unserializeDataProvider() + { + return [ + ['""', ''], + ['"string"', 'string'], + ['null', null], + ['false', false], + ['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]], + ['123', 123], + ['10.56', 10.56], + ['{}', []], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php new file mode 100644 index 0000000000000..5d1e34a542826 --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php @@ -0,0 +1,71 @@ +serialize = $objectManager->getObject(Serialize::class); + } + + /** + * @param string|int|float|bool|array|null $value + * @param string $serializedValue + * @dataProvider serializeDataProvider + */ + public function testSerialize($value, $serializedValue) + { + $this->assertEquals($serializedValue, $this->serialize->serialize($value)); + } + + public function serializeDataProvider() + { + return [ + ['string', 's:6:"string";'], + ['', 's:0:"";'], + [10, 'i:10;'], + [10.5, 'd:10.5;'], + [null, 'N;'], + [false, 'b:0;'], + [['foo' => 'bar'], 'a:1:{s:3:"foo";s:3:"bar";}'], + ]; + } + + /** + * @param string $serializedValue + * @param string|int|float|bool|array|null $value + * @dataProvider unserializeDataProvider + */ + public function testUnserialize($serializedValue, $value) + { + $this->assertEquals($value, $this->serialize->unserialize($serializedValue)); + } + + public function unserializeDataProvider() + { + return [ + ['s:6:"string";', 'string'], + ['s:0:"";', ''], + ['i:10;', 10], + ['d:10.5;', 10.5], + ['N;', null], + ['b:0;', false], + ['a:1:{s:3:"foo";s:3:"bar";}', ['foo' => 'bar']], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Session/Config.php b/lib/internal/Magento/Framework/Session/Config.php index 2e9fa44a794a8..7c4fc08a5db9a 100644 --- a/lib/internal/Magento/Framework/Session/Config.php +++ b/lib/internal/Magento/Framework/Session/Config.php @@ -2,7 +2,7 @@ /** * Session configuration object * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/Config/ConfigInterface.php b/lib/internal/Magento/Framework/Session/Config/ConfigInterface.php index f4928f392a71f..6616796dfd9f5 100644 --- a/lib/internal/Magento/Framework/Session/Config/ConfigInterface.php +++ b/lib/internal/Magento/Framework/Session/Config/ConfigInterface.php @@ -2,7 +2,7 @@ /** * Session config interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session\Config; diff --git a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php index 265ef90d0873b..80dd72b5a66ff 100644 --- a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php +++ b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php @@ -1,6 +1,6 @@ expireSessionCookie(); diff --git a/lib/internal/Magento/Framework/Session/SessionManagerInterface.php b/lib/internal/Magento/Framework/Session/SessionManagerInterface.php index ab6dbb225a8ff..f641027ef186a 100644 --- a/lib/internal/Magento/Framework/Session/SessionManagerInterface.php +++ b/lib/internal/Magento/Framework/Session/SessionManagerInterface.php @@ -2,7 +2,7 @@ /** * Magento session manager interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/SidResolver.php b/lib/internal/Magento/Framework/Session/SidResolver.php index e8006d644e48c..1bf6282405be6 100644 --- a/lib/internal/Magento/Framework/Session/SidResolver.php +++ b/lib/internal/Magento/Framework/Session/SidResolver.php @@ -2,7 +2,7 @@ /** * SID resolver * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/SidResolverInterface.php b/lib/internal/Magento/Framework/Session/SidResolverInterface.php index d60575e59fe21..8b51198b17de9 100644 --- a/lib/internal/Magento/Framework/Session/SidResolverInterface.php +++ b/lib/internal/Magento/Framework/Session/SidResolverInterface.php @@ -2,7 +2,7 @@ /** * SID resolver interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/Storage.php b/lib/internal/Magento/Framework/Session/Storage.php index aed4c88b76936..eda48b74e2854 100644 --- a/lib/internal/Magento/Framework/Session/Storage.php +++ b/lib/internal/Magento/Framework/Session/Storage.php @@ -2,7 +2,7 @@ /** * Default session storage * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/StorageInterface.php b/lib/internal/Magento/Framework/Session/StorageInterface.php index a842f14860b94..30d54cc151b0b 100644 --- a/lib/internal/Magento/Framework/Session/StorageInterface.php +++ b/lib/internal/Magento/Framework/Session/StorageInterface.php @@ -2,7 +2,7 @@ /** * Session storage interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Session; diff --git a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php index 1df7cfa69ca83..183aaabc12fbc 100644 --- a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php +++ b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php @@ -1,6 +1,6 @@ installationWritableDirectories[$code] = $this->directoryList->getPath($code); @@ -130,7 +134,7 @@ public function getInstallationCurrentWritableDirectories() } /** - * Check all sub-directories and files except for var/generation and var/di + * Check all sub-directories and files except for generated/code and generated/metadata * * @param string $directory * @return bool @@ -142,8 +146,8 @@ private function checkRecursiveDirectories($directory) \RecursiveIteratorIterator::CHILD_FIRST ); $noWritableFilesFolders = [ - $this->directoryList->getPath(DirectoryList::GENERATION) . '/', - $this->directoryList->getPath(DirectoryList::DI) . '/', + $this->directoryList->getPath(DirectoryList::GENERATED_CODE) . '/', + $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/', ]; $directoryIterator = new Filter($directoryIterator, $noWritableFilesFolders); diff --git a/lib/internal/Magento/Framework/Setup/InstallDataInterface.php b/lib/internal/Magento/Framework/Setup/InstallDataInterface.php index 24f7a18ae6986..e3f4ccf2cf375 100644 --- a/lib/internal/Magento/Framework/Setup/InstallDataInterface.php +++ b/lib/internal/Magento/Framework/Setup/InstallDataInterface.php @@ -1,6 +1,6 @@ assertEquals($expected, $this->filePermissions->getInstallationWritableDirectories()); @@ -157,6 +158,7 @@ public function testGetMissingWritableDirectoriesAndPathsForInstallation() BP . '/var', BP . '/pub/media', BP . '/pub/static', + BP . '/generated' ]; $this->assertEquals( @@ -231,6 +233,11 @@ public function setUpDirectoryListInstallation() ->method('getPath') ->with(DirectoryList::STATIC_VIEW) ->will($this->returnValue(BP . '/pub/static')); + $this->directoryListMock + ->expects($this->at(4)) + ->method('getPath') + ->with(DirectoryList::GENERATED) + ->will($this->returnValue(BP . '/generated')); } public function setUpDirectoryWriteInstallation() diff --git a/lib/internal/Magento/Framework/Setup/Test/Unit/ListsTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/ListsTest.php index 4ce704c3b6adf..a3728abbfbc2a 100644 --- a/lib/internal/Magento/Framework/Setup/Test/Unit/ListsTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/ListsTest.php @@ -1,6 +1,6 @@ @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/extend_data.xml b/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/extend_data.xml index c070901c5d978..fc75234d3b471 100644 --- a/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/extend_data.xml +++ b/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/extend_data.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/mixed_data.xml b/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/mixed_data.xml index 193d0c752369e..34356fdd6dbac 100644 --- a/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/mixed_data.xml +++ b/lib/internal/Magento/Framework/Simplexml/Test/Unit/_files/mixed_data.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Stdlib/ArrayManager.php b/lib/internal/Magento/Framework/Stdlib/ArrayManager.php index f8b758bb9d9df..2ee1a23ce8380 100644 --- a/lib/internal/Magento/Framework/Stdlib/ArrayManager.php +++ b/lib/internal/Magento/Framework/Stdlib/ArrayManager.php @@ -1,6 +1,6 @@ ['existing' => ['path' => 1]], 'value' => 'valuable data', 'result' => ['existing' => ['path' => 1], 'new' => ['path' => [2 => 'valuable data']]] + ], + 3 => [ + 'path' => ['new', 'path/2'], + 'data' => ['existing' => ['path' => 1]], + 'value' => 'valuable data', + 'result' => ['existing' => ['path' => 1], 'new' => ['path' => [2 => 'valuable data']]] ] ]; } @@ -178,6 +184,12 @@ public function setReplaceProvider() 'data' => ['existing' => ['path' => 1]], 'value' => 'valuable data', 'result' => ['existing' => ['path' => 1]] + ], + 3 => [ + 'path' => ['new', 'path', '2'], + 'data' => ['existing' => ['path' => 1]], + 'value' => 'valuable data', + 'result' => ['existing' => ['path' => 1]] ] ]; } @@ -228,6 +240,13 @@ public function moveDataProvider() 'data' => ['valid' => ['path' => 'value'], 'target' => ['path' => 'exists']], 'overwrite' => true, 'result' => ['valid' => [], 'target' => ['path' => 'value']] + ], + 4 => [ + 'path' => ['valid', 'path'], + 'targetPath' => 'target/path', + 'data' => ['valid' => ['path' => 'value'], 'target' => ['path' => 'exists']], + 'overwrite' => true, + 'result' => ['valid' => [], 'target' => ['path' => 'value']] ] ]; } @@ -267,7 +286,13 @@ public function mergeDataProvider() 'data' => [], 'value' => [true], 'result' => [] - ] + ], + 3 => [ + 'path' => ['0', 'path/1'], + 'data' => [['path' => [false, ['value' => false]]]], + 'value' => ['value' => true, 'new_value' => false], + 'result' => [['path' => [false, ['value' => true, 'new_value' => false]]]] + ], ]; } @@ -337,7 +362,12 @@ public function removeDataProvider() 'path' => 'invalid', 'data' => [true], 'result' => [true] - ] + ], + 3 => [ + 'path' => ['simple'], + 'data' => ['simple' => true, 'complex' => false], + 'result' => ['complex' => false] + ], ]; } @@ -550,7 +580,7 @@ public function slicePathDataProvider() 'offset' => -6, 'length' => 3, 'result' => 'path/0/goes' - ] + ], ]; } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/ArrayUtilsTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/ArrayUtilsTest.php index d9786ff071c7e..cf8f821cc8684 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/ArrayUtilsTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/ArrayUtilsTest.php @@ -1,6 +1,6 @@ batchSize = 10; + $this->currentBatch = 0; + $this->correlationName = 'correlationName'; + $this->rangeField = 'rangeField'; + $this->rangeFieldAlias = 'rangeFieldAlias'; + + $this->selectMock = $this->getMock(Select::class, [], [], '', false, false); + $this->wrapperSelectMock = $this->getMock(Select::class, [], [], '', false, false); + $this->connectionMock = $this->getMock(AdapterInterface::class); + $this->connectionMock->expects($this->any())->method('select')->willReturn($this->wrapperSelectMock); + $this->selectMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); + $this->connectionMock->expects($this->any())->method('quoteIdentifier')->willReturnArgument(0); + + $this->model = new BatchRangeIterator( + $this->selectMock, + $this->batchSize, + $this->correlationName, + $this->rangeField, + $this->rangeFieldAlias + ); + } + + /** + * Test steps: + * 1. $iterator->current(); + * 2. $iterator->key(); + * @return void + */ + public function testCurrent() + { + $this->selectMock->expects($this->once())->method('limit')->with($this->currentBatch, $this->batchSize); + $this->selectMock->expects($this->once())->method('order')->with('correlationName.rangeField' . ' ASC'); + $this->assertEquals($this->selectMock, $this->model->current()); + $this->assertEquals(0, $this->model->key()); + } + + /** + * Test the separation of batches + */ + public function testIterations() + { + $iterations = 0; + + $this->connectionMock->expects($this->once()) + ->method('fetchRow') + ->willReturn(['cnt' => 105]); + + foreach ($this->model as $key) { + $this->assertEquals($this->selectMock, $key); + $iterations++; + }; + + $this->assertEquals(10, $iterations); + } +} diff --git a/lib/internal/Magento/Framework/Test/Unit/DB/Query/GeneratorTest.php b/lib/internal/Magento/Framework/Test/Unit/DB/Query/GeneratorTest.php index 4be22eb14ca85..28e06506f66ce 100644 --- a/lib/internal/Magento/Framework/Test/Unit/DB/Query/GeneratorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/DB/Query/GeneratorTest.php @@ -1,12 +1,13 @@ factoryMock = $this->getMock(BatchIteratorFactory::class, [], [], '', false, false); + $this->rangeFactoryMock = $this->getMock(BatchRangeIteratorFactory::class, ['create'], [], '', false, false); $this->selectMock = $this->getMock(Select::class, [], [], '', false, false); $this->iteratorMock = $this->getMock(BatchIterator::class, [], [], '', false, false); - $this->model = new Generator($this->factoryMock); + $this->model = new Generator($this->factoryMock, $this->rangeFactoryMock); } /** @@ -165,4 +172,40 @@ public function testGenerateWithInvalidWithWildcard() )->willReturn($this->iteratorMock); $this->assertEquals($this->iteratorMock, $this->model->generate('entity_id', $this->selectMock, 100)); } + + /** + * Test success generate with non-unique strategy. + * @return void + */ + public function testGenerateWithNonUniqueStrategy() + { + $map = [ + [ + Select::FROM, + [ + 'cp' => ['joinType' => Select::FROM] + ] + ], + [ + Select::COLUMNS, + [ + ['cp', 'entity_id', 'product_id'] + ] + ] + ]; + $this->selectMock->expects($this->exactly(2))->method('getPart')->willReturnMap($map); + $this->factoryMock->expects($this->once())->method('create')->with( + [ + 'select' => $this->selectMock, + 'batchSize' => 100, + 'correlationName' => 'cp', + 'rangeField' => 'entity_id', + 'rangeFieldAlias' => 'product_id' + ] + )->willReturn($this->iteratorMock); + $this->assertEquals( + $this->iteratorMock, + $this->model->generate('entity_id', $this->selectMock, 100, 'non_unique') + ); + } } diff --git a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php index 2d15510baebd9..75e6e7892f57f 100644 --- a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php @@ -1,6 +1,6 @@ 'synchronize']; @@ -71,13 +81,22 @@ protected function createInstance(array $data = []) $resourceCollection = $this->getMockBuilder(\Magento\Framework\Data\Collection\AbstractDb::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->json = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) + ->setMethods(null) + ->getMock(); + + $this->serialize = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Serialize::class) + ->setMethods(null) + ->getMock(); $this->flag = new \Magento\Framework\Flag( $context, $registry, $resource, $resourceCollection, - $data + $data, + $this->json, + $this->serialize ); } @@ -94,7 +113,17 @@ public function testConstruct() $this->assertEquals($flagCode, $this->flag->getFlagCode()); } - public function testGetFlagData() + public function testGetFlagDataJson() + { + $result = $this->flag->getFlagData(); + $this->assertNull($result); + $flagData = json_encode('data'); + $this->flag->setData('flag_data', $flagData); + $result = $this->flag->getFlagData(); + $this->assertEquals(json_decode($flagData), $result); + } + + public function testGetFlagDataSerialized() { $result = $this->flag->getFlagData(); $this->assertNull($result); @@ -108,7 +137,7 @@ public function testSetFlagData() { $flagData = 'data'; $this->flag->setFlagData($flagData); - $result = unserialize($this->flag->getData('flag_data')); + $result = json_decode($this->flag->getData('flag_data')); $this->assertEquals($flagData, $result); } diff --git a/lib/internal/Magento/Framework/Test/Unit/Interception/InterceptorTest.php b/lib/internal/Magento/Framework/Test/Unit/Interception/InterceptorTest.php index 7b3624c501d8d..0da6ef45e3b2a 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Interception/InterceptorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Interception/InterceptorTest.php @@ -1,6 +1,6 @@ viewDesign = $this->getMock(\Magento\Framework\View\DesignInterface::class, [], [], '', false); $this->cache = $this->getMock(\Magento\Framework\Cache\FrontendInterface::class, [], [], '', false); $this->viewFileSystem = $this->getMock(\Magento\Framework\View\FileSystem::class, [], [], '', false); @@ -104,6 +106,21 @@ protected function setUp() $this->csvParser, $this->packDictionary ); + + $serializerMock = $this->getMock(SerializerInterface::class); + $serializerMock->method('serialize') + ->willReturnCallback(function ($data) { + return json_encode($data); + }); + $serializerMock->method('unserialize') + ->willReturnCallback(function ($string) { + return json_decode($string, true); + }); + $objectManager->setBackwardCompatibleProperty( + $this->translate, + 'serializer', + $serializerMock + ); } /** @@ -119,7 +136,7 @@ public function testLoadData($area, $forceReload, $cachedData) $this->cache->expects($this->exactly($forceReload ? 0 : 1)) ->method('load') - ->will($this->returnValue(serialize($cachedData))); + ->will($this->returnValue(json_encode($cachedData))); if (!$forceReload && $cachedData !== false) { $this->translate->loadData($area, $forceReload); @@ -222,7 +239,7 @@ public function testGetData($data, $result) { $this->cache->expects($this->once()) ->method('load') - ->will($this->returnValue(serialize($data))); + ->will($this->returnValue(json_encode($data))); $this->expectsSetConfig('themeId'); $this->translate->loadData('frontend'); $this->assertEquals($result, $this->translate->getData()); diff --git a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php index 36fce179395b5..cfb4fb884accb 100644 --- a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php @@ -1,12 +1,13 @@ routeParamsResolverMock = $this->getMock( @@ -549,18 +555,17 @@ public function testAddSessionParam() /** * @param bool $result - * @param string $baseUrl * @param string $referrer * @dataProvider isOwnOriginUrlDataProvider */ - public function testIsOwnOriginUrl($result, $baseUrl, $referrer) + public function testIsOwnOriginUrl($result, $referrer) { $requestMock = $this->getRequestMock(); - $model = $this->getUrlModel(['scopeResolver' => $this->scopeResolverMock, 'request' => $requestMock]); + $this->hostChecker = $this->getMockBuilder(HostChecker::class) + ->disableOriginalConstructor()->getMock(); + $this->hostChecker->expects($this->once())->method('isOwnOrigin')->with($referrer)->willReturn($result); + $model = $this->getUrlModel(['hostChecker' => $this->hostChecker, 'request' => $requestMock]); - $this->scopeMock->expects($this->any())->method('getBaseUrl')->will($this->returnValue($baseUrl)); - $this->scopeResolverMock->expects($this->any())->method('getScopes') - ->will($this->returnValue([$this->scopeMock])); $requestMock->expects($this->once())->method('getServer')->with('HTTP_REFERER') ->will($this->returnValue($referrer)); @@ -570,8 +575,8 @@ public function testIsOwnOriginUrl($result, $baseUrl, $referrer) public function isOwnOriginUrlDataProvider() { return [ - 'is origin url' => [true, 'http://localhost/', 'http://localhost/'], - 'is not origin url' => [false, 'http://localhost/', 'http://example.com/'], + 'is origin url' => [true, 'http://localhost/'], + 'is not origin url' => [false, 'http://example.com/'], ]; } diff --git a/lib/internal/Magento/Framework/Test/Unit/UtilTest.php b/lib/internal/Magento/Framework/Test/Unit/UtilTest.php index 8ca76e375b8b7..a356349d85684 100644 --- a/lib/internal/Magento/Framework/Test/Unit/UtilTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/UtilTest.php @@ -2,7 +2,7 @@ /** * Collection of various useful functions * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Test\Unit; diff --git a/lib/internal/Magento/Framework/Test/Unit/ValidatorFactoryTest.php b/lib/internal/Magento/Framework/Test/Unit/ValidatorFactoryTest.php index 2c8dca01988e7..9474e72651fce 100644 --- a/lib/internal/Magento/Framework/Test/Unit/ValidatorFactoryTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/ValidatorFactoryTest.php @@ -2,7 +2,7 @@ /** * Unit test for Magento\Framework\ValidatorFactory * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/lib/internal/Magento/Framework/Test/Unit/ValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/ValidatorTest.php index af48d24de887d..6745f4e6c0544 100644 --- a/lib/internal/Magento/Framework/Test/Unit/ValidatorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/ValidatorTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xml b/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xml index 8eee79f23b73e..2afb2f8379e9b 100644 --- a/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xml +++ b/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xsd b/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xsd index aec264cdb4173..4640abfcf07e8 100644 --- a/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xsd +++ b/lib/internal/Magento/Framework/TestFramework/Test/Unit/Unit/Utility/_files/valid.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/TestFramework/Unit/AbstractFactoryTestCase.php b/lib/internal/Magento/Framework/TestFramework/Unit/AbstractFactoryTestCase.php index 1efff6d6761dd..87c0f48eb7c82 100644 --- a/lib/internal/Magento/Framework/TestFramework/Unit/AbstractFactoryTestCase.php +++ b/lib/internal/Magento/Framework/TestFramework/Unit/AbstractFactoryTestCase.php @@ -2,7 +2,7 @@ /** * Framework for unit tests containing helper methods * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. * * Number of fields is necessary because of the number of fields used by multiple layers diff --git a/lib/internal/Magento/Framework/TestFramework/Unit/Autoloader/ExtensionGeneratorAutoloader.php b/lib/internal/Magento/Framework/TestFramework/Unit/Autoloader/ExtensionGeneratorAutoloader.php index 4e0fcac1fdff2..9f8cb85b76ecd 100644 --- a/lib/internal/Magento/Framework/TestFramework/Unit/Autoloader/ExtensionGeneratorAutoloader.php +++ b/lib/internal/Magento/Framework/TestFramework/Unit/Autoloader/ExtensionGeneratorAutoloader.php @@ -1,6 +1,6 @@ getProperty($propertyName); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($object, $propertyValue); diff --git a/lib/internal/Magento/Framework/TestFramework/Unit/Helper/ProxyTesting.php b/lib/internal/Magento/Framework/TestFramework/Unit/Helper/ProxyTesting.php index 85d21ed2d1a75..077dc7e2bea6f 100644 --- a/lib/internal/Magento/Framework/TestFramework/Unit/Helper/ProxyTesting.php +++ b/lib/internal/Magento/Framework/TestFramework/Unit/Helper/ProxyTesting.php @@ -1,6 +1,6 @@ _cache->load($this->getCacheId()); if ($data) { - $data = unserialize($data); + $data = $this->getSerializer()->unserialize($data); } return $data; } @@ -486,7 +491,22 @@ protected function _loadCache() */ protected function _saveCache() { - $this->_cache->save(serialize($this->getData()), $this->getCacheId(true), [], false); + $this->_cache->save($this->getSerializer()->serialize($this->getData()), $this->getCacheId(true), [], false); return $this; } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(Serialize\SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/Translate/AbstractAdapter.php b/lib/internal/Magento/Framework/Translate/AbstractAdapter.php index 2f0ceb7735eae..cd43d61074f52 100644 --- a/lib/internal/Magento/Framework/Translate/AbstractAdapter.php +++ b/lib/internal/Magento/Framework/Translate/AbstractAdapter.php @@ -1,6 +1,6 @@ _request = $request; $this->_routeConfig = $routeConfig; @@ -218,6 +227,8 @@ public function __construct( $this->_scopeConfig = $scopeConfig; $this->routeParamsPreprocessor = $routeParamsPreprocessor; $this->_scopeType = $scopeType; + $this->hostChecker = $hostChecker ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(HostChecker::class); parent::__construct($data); } @@ -1086,17 +1097,7 @@ public function useSessionIdForUrl($secure = false) */ public function isOwnOriginUrl() { - $scopeDomains = []; - $referer = parse_url($this->_request->getServer('HTTP_REFERER'), PHP_URL_HOST); - foreach ($this->_scopeResolver->getScopes() as $scope) { - $scopeDomains[] = parse_url($scope->getBaseUrl(), PHP_URL_HOST); - $scopeDomains[] = parse_url($scope->getBaseUrl(UrlInterface::URL_TYPE_LINK, true), PHP_URL_HOST); - } - $scopeDomains = array_unique($scopeDomains); - if (empty($referer) || in_array($referer, $scopeDomains)) { - return true; - } - return false; + return $this->hostChecker->isOwnOrigin($this->_request->getServer('HTTP_REFERER')); } /** @@ -1163,7 +1164,7 @@ protected function getRouteParamsResolver() private function getUrlModifier() { if ($this->urlModifier === null) { - $this->urlModifier = \Magento\Framework\App\ObjectManager::getInstance()->get( + $this->urlModifier = \Magento\Framework\App\ObjectManager::getInstance()->get( \Magento\Framework\Url\ModifierInterface::class ); } diff --git a/lib/internal/Magento/Framework/Url/Decoder.php b/lib/internal/Magento/Framework/Url/Decoder.php index d60611062d076..06a865db0079f 100644 --- a/lib/internal/Magento/Framework/Url/Decoder.php +++ b/lib/internal/Magento/Framework/Url/Decoder.php @@ -1,6 +1,6 @@ scopeResolver = $scopeResolver; + } + + /** + * Check if provided URL is one of the domain URLs assigned to scopes + * + * @param string $url + * @return bool + */ + public function isOwnOrigin($url) + { + $scopeHostNames = []; + $hostName = parse_url($url, PHP_URL_HOST); + if (empty($hostName)) { + return true; + } + foreach ($this->scopeResolver->getScopes() as $scope) { + $scopeHostNames[] = parse_url($scope->getBaseUrl(), PHP_URL_HOST); + $scopeHostNames[] = parse_url($scope->getBaseUrl(UrlInterface::URL_TYPE_LINK, true), PHP_URL_HOST); + } + $scopeHostNames = array_unique($scopeHostNames); + return in_array($hostName, $scopeHostNames); + } +} diff --git a/lib/internal/Magento/Framework/Url/ModifierComposite.php b/lib/internal/Magento/Framework/Url/ModifierComposite.php index 044faf23182de..1f301f4ae5e83 100644 --- a/lib/internal/Magento/Framework/Url/ModifierComposite.php +++ b/lib/internal/Magento/Framework/Url/ModifierComposite.php @@ -1,6 +1,6 @@ scopeResolver = $this->getMockBuilder( + \Magento\Framework\Url\ScopeResolverInterface::class + )->getMock(); + + $objectManager = new ObjectManager($this); + $this->object = $objectManager->getObject( + \Magento\Framework\Url\HostChecker::class, + [ + 'scopeResolver' => $this->scopeResolver + ] + ); + } + + /** + * @dataProvider isOwnOriginDataProvider + * @param string $url + * @param boolean $result + */ + public function testIsOwnOrigin($url, $result) + { + $scopes[0] = $this->getMockBuilder(\Magento\Framework\Url\ScopeInterface::class)->getMock(); + $scopes[0]->expects($this->any())->method('getBaseUrl')->willReturn('http://www.example.com'); + $scopes[1] = $this->getMockBuilder(\Magento\Framework\Url\ScopeInterface::class)->getMock(); + $scopes[1]->expects($this->any())->method('getBaseUrl')->willReturn('https://www.example2.com'); + + $this->scopeResolver->expects($this->atLeastOnce())->method('getScopes')->willReturn($scopes); + + $this->assertEquals($result, $this->object->isOwnOrigin($url)); + } + + /** + * @return array + */ + public function isOwnOriginDataProvider() + { + return [ + ['http://www.example.com/some/page/', true], + ['http://www.test.com/other/page/', false], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Url/Test/Unit/QueryParamsResolverTest.php b/lib/internal/Magento/Framework/Url/Test/Unit/QueryParamsResolverTest.php index ea94ea270eb17..ca53f9ef2273a 100644 --- a/lib/internal/Magento/Framework/Url/Test/Unit/QueryParamsResolverTest.php +++ b/lib/internal/Magento/Framework/Url/Test/Unit/QueryParamsResolverTest.php @@ -1,6 +1,6 @@ listOfProtocols = $listOfProtocols; + } + } + + /** + * Validate URI + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $uri = new Uri($value); + $isValid = in_array( + strtolower($uri->getScheme()), + $this->listOfProtocols + ); + if (!$isValid) { + $this->_addMessages(["Protocol isn't allowed"]); + } + return $isValid; + } +} diff --git a/lib/internal/Magento/Framework/Validator/Alnum.php b/lib/internal/Magento/Framework/Validator/Alnum.php index da0bc22ebc708..bc3470b54c3db 100644 --- a/lib/internal/Magento/Framework/Validator/Alnum.php +++ b/lib/internal/Magento/Framework/Validator/Alnum.php @@ -2,7 +2,7 @@ /** * Alphanumerical validator * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/Builder.php b/lib/internal/Magento/Framework/Validator/Builder.php index e31a2c86d9238..fe5465124d878 100644 --- a/lib/internal/Magento/Framework/Validator/Builder.php +++ b/lib/internal/Magento/Framework/Validator/Builder.php @@ -2,7 +2,7 @@ /** * Magento Validator Builder * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/lib/internal/Magento/Framework/Validator/Config.php b/lib/internal/Magento/Framework/Validator/Config.php index dc0d6a6259db2..7860fc811220c 100644 --- a/lib/internal/Magento/Framework/Validator/Config.php +++ b/lib/internal/Magento/Framework/Validator/Config.php @@ -2,7 +2,7 @@ /** * Validation configuration files handler * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/Constraint.php b/lib/internal/Magento/Framework/Validator/Constraint.php index 3e40085397121..af87ba8e9029d 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint.php +++ b/lib/internal/Magento/Framework/Validator/Constraint.php @@ -2,7 +2,7 @@ /** * Validator constraint delegates validation to wrapped validator. * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/Constraint/Option.php b/lib/internal/Magento/Framework/Validator/Constraint/Option.php index 2ce72ca0c62a2..55988c73c3c79 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint/Option.php +++ b/lib/internal/Magento/Framework/Validator/Constraint/Option.php @@ -2,7 +2,7 @@ /** * Constraint option * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\Constraint; diff --git a/lib/internal/Magento/Framework/Validator/Constraint/Option/Callback.php b/lib/internal/Magento/Framework/Validator/Constraint/Option/Callback.php index 201aac589647a..4b704902af897 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint/Option/Callback.php +++ b/lib/internal/Magento/Framework/Validator/Constraint/Option/Callback.php @@ -2,7 +2,7 @@ /** * Constraint callback option * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\Constraint\Option; diff --git a/lib/internal/Magento/Framework/Validator/Constraint/OptionInterface.php b/lib/internal/Magento/Framework/Validator/Constraint/OptionInterface.php index 2b63986c82e53..1c94a804b579a 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint/OptionInterface.php +++ b/lib/internal/Magento/Framework/Validator/Constraint/OptionInterface.php @@ -2,7 +2,7 @@ /** * Validator Constraint Option interface * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\Constraint; diff --git a/lib/internal/Magento/Framework/Validator/Constraint/Property.php b/lib/internal/Magento/Framework/Validator/Constraint/Property.php index c40e3c1237e64..8178445796f50 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint/Property.php +++ b/lib/internal/Magento/Framework/Validator/Constraint/Property.php @@ -2,7 +2,7 @@ /** * Validator constraint delegates validation of value's property to wrapped validator. * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/lib/internal/Magento/Framework/Validator/ConstraintFactory.php b/lib/internal/Magento/Framework/Validator/ConstraintFactory.php index 1077f21bfa2bd..dd56b49efc5dd 100644 --- a/lib/internal/Magento/Framework/Validator/ConstraintFactory.php +++ b/lib/internal/Magento/Framework/Validator/ConstraintFactory.php @@ -1,6 +1,6 @@ _configFiles = $this->cache->load(self::CACHE_KEY); if (!$this->_configFiles) { $this->_configFiles = $this->moduleReader->getConfigurationFiles('validation.xml'); - $this->cache->save(serialize($this->_configFiles), self::CACHE_KEY); + $this->cache->save($this->getSerializer()->serialize($this->_configFiles->toArray()), self::CACHE_KEY); } else { - $this->_configFiles = unserialize($this->_configFiles); + $filesArray = $this->getSerializer()->unserialize($this->_configFiles); + $this->_configFiles = $this->getFileIteratorFactory()->create(array_keys($filesArray)); } } } @@ -109,7 +120,7 @@ public function getValidatorConfig() { $this->_initializeConfigList(); $this->_initializeDefaultTranslator(); - return $this->_objectManager->create( + return $this->_objectManager->create( \Magento\Framework\Validator\Config::class, ['configFiles' => $this->_configFiles]); } @@ -140,4 +151,33 @@ public function createValidator($entityName, $groupName, array $builderConfig = $this->_initializeDefaultTranslator(); return $this->getValidatorConfig()->createValidator($entityName, $groupName, $builderConfig); } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = $this->_objectManager->get(\Magento\Framework\Serialize\SerializerInterface::class); + } + return $this->serializer; + } + + /** + * Get file iterator factory + * + * @return \Magento\Framework\Config\FileIteratorFactory + * @deprecated + */ + private function getFileIteratorFactory() + { + if ($this->fileIteratorFactory === null) { + $this->fileIteratorFactory = $this->_objectManager + ->get(\Magento\Framework\Config\FileIteratorFactory::class); + } + return $this->fileIteratorFactory; + } } diff --git a/lib/internal/Magento/Framework/Validator/File/Extension.php b/lib/internal/Magento/Framework/Validator/File/Extension.php index dfc81aa1fd751..727cfbbf3ebfc 100644 --- a/lib/internal/Magento/Framework/Validator/File/Extension.php +++ b/lib/internal/Magento/Framework/Validator/File/Extension.php @@ -2,7 +2,7 @@ /** * Validator for the file extension of a file * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\File; diff --git a/lib/internal/Magento/Framework/Validator/File/ImageSize.php b/lib/internal/Magento/Framework/Validator/File/ImageSize.php index 957190a3b5f27..e81a4277400eb 100644 --- a/lib/internal/Magento/Framework/Validator/File/ImageSize.php +++ b/lib/internal/Magento/Framework/Validator/File/ImageSize.php @@ -2,7 +2,7 @@ /** * Validator for the image size of a image file * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\File; diff --git a/lib/internal/Magento/Framework/Validator/File/IsImage.php b/lib/internal/Magento/Framework/Validator/File/IsImage.php index 8af98b562a62b..f1c829bbe26bf 100644 --- a/lib/internal/Magento/Framework/Validator/File/IsImage.php +++ b/lib/internal/Magento/Framework/Validator/File/IsImage.php @@ -2,7 +2,7 @@ /** * Validator which checks if the file is image * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\File; diff --git a/lib/internal/Magento/Framework/Validator/File/Size.php b/lib/internal/Magento/Framework/Validator/File/Size.php index a4a058a35294f..e438e9b892b5a 100644 --- a/lib/internal/Magento/Framework/Validator/File/Size.php +++ b/lib/internal/Magento/Framework/Validator/File/Size.php @@ -2,7 +2,7 @@ /** * Validator for the maximum size of a file up to a max of 2GB * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator\File; diff --git a/lib/internal/Magento/Framework/Validator/FloatUtils.php b/lib/internal/Magento/Framework/Validator/FloatUtils.php index 94b151ce7c887..4588731f0f48e 100644 --- a/lib/internal/Magento/Framework/Validator/FloatUtils.php +++ b/lib/internal/Magento/Framework/Validator/FloatUtils.php @@ -2,7 +2,7 @@ /** * Float validator * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/IntUtils.php b/lib/internal/Magento/Framework/Validator/IntUtils.php index b37b706686d81..2a821c8652233 100644 --- a/lib/internal/Magento/Framework/Validator/IntUtils.php +++ b/lib/internal/Magento/Framework/Validator/IntUtils.php @@ -2,7 +2,7 @@ /** * Integer validator * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/Ip.php b/lib/internal/Magento/Framework/Validator/Ip.php index d01b6adcc956c..820960b9173e4 100644 --- a/lib/internal/Magento/Framework/Validator/Ip.php +++ b/lib/internal/Magento/Framework/Validator/Ip.php @@ -2,7 +2,7 @@ /** * Ip validator * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Validator; diff --git a/lib/internal/Magento/Framework/Validator/Locale.php b/lib/internal/Magento/Framework/Validator/Locale.php index 2717a97a47fe5..f38b3db3ece1c 100644 --- a/lib/internal/Magento/Framework/Validator/Locale.php +++ b/lib/internal/Magento/Framework/Validator/Locale.php @@ -1,6 +1,6 @@ _defaultTranslator = \Magento\Framework\Validator\AbstractValidator::getDefaultTranslator(); - $this->_objectManager = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $this->_validatorConfig = $this->getMockBuilder( - \Magento\Framework\Validator\Config::class - )->setMethods( - ['createValidatorBuilder', 'createValidator'] - )->disableOriginalConstructor()->getMock(); - - $this->_objectManager->expects( - $this->at(0) - )->method( - 'create' - )->with( - \Magento\Framework\Translate\Adapter::class - )->will( - $this->returnValue(new \Magento\Framework\Translate\Adapter()) - ); + $this->defaultTranslator = \Magento\Framework\Validator\AbstractValidator::getDefaultTranslator(); - $this->_objectManager->expects( - $this->at(1) - )->method( - 'create' - )->with( + $this->objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); + $this->validatorConfigMock = $this->getMock( \Magento\Framework\Validator\Config::class, - ['configFiles' => ['/tmp/moduleOne/etc/validation.xml']] - )->will( - $this->returnValue($this->_validatorConfig) + ['createValidatorBuilder', 'createValidator'], + [], + '', + false ); - - // Config mock - $this->_config = $this->getMockBuilder( - \Magento\Framework\Module\Dir\Reader::class - )->setMethods( - ['getConfigurationFiles'] - )->disableOriginalConstructor()->getMock(); - $this->_config->expects( - $this->once() - )->method( - 'getConfigurationFiles' - )->with( - 'validation.xml' - )->will( - $this->returnValue(['/tmp/moduleOne/etc/validation.xml']) + $translateAdapterMock = $this->getMock(\Magento\Framework\Translate\Adapter::class, [], [], '', false); + $this->objectManagerMock->expects($this->at(0)) + ->method('create') + ->with(\Magento\Framework\Translate\Adapter::class) + ->willReturn($translateAdapterMock); + $this->fileIteratorMock = $this->getMock( + \Magento\Framework\Config\FileIterator::class, + [], + [], + '', + false + ); + $this->objectManagerMock->expects($this->at(1)) + ->method('create') + ->with( + \Magento\Framework\Validator\Config::class, + ['configFiles' => $this->fileIteratorMock] + ) + ->willReturn($this->validatorConfigMock); + $this->readerMock = $this->getMock( + \Magento\Framework\Module\Dir\Reader::class, + ['getConfigurationFiles'], + [], + '', + false ); + $this->cacheMock = $this->getMock(\Magento\Framework\Cache\FrontendInterface::class); - // Translate adapter mock - $this->_translateAdapter = $this->getMockBuilder( - \Magento\Framework\TranslateInterface::class - )->disableOriginalConstructor()->getMock(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->factory = $objectManager->getObject( + \Magento\Framework\Validator\Factory::class, + [ + 'objectManager' => $this->objectManagerMock, + 'moduleReader' => $this->readerMock, + 'cache' => $this->cacheMock + ] + ); - $this->cache = $this->getMockBuilder(\Magento\Framework\Cache\FrontendInterface::class) - ->getMockForAbstractClass(); + $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\SerializerInterface::class); + $this->fileIteratorFactoryMock = $this->getMock( + \Magento\Framework\Config\FileIteratorFactory::class, + [], + [], + '', + false + ); + $objectManager->setBackwardCompatibleProperty( + $this->factory, + 'serializer', + $this->serializerMock + ); + $objectManager->setBackwardCompatibleProperty( + $this->factory, + 'fileIteratorFactory', + $this->fileIteratorFactoryMock + ); } /** @@ -100,87 +141,103 @@ protected function setUp() */ protected function tearDown() { - \Magento\Framework\Validator\AbstractValidator::setDefaultTranslator($this->_defaultTranslator); - unset($this->_defaultTranslator); + \Magento\Framework\Validator\AbstractValidator::setDefaultTranslator($this->defaultTranslator); + unset($this->defaultTranslator); } - /** - * Test getValidatorConfig created correct validator config. Check that validator translator was initialized. - */ public function testGetValidatorConfig() { - $factory = new \Magento\Framework\Validator\Factory( - $this->_objectManager, - $this->_config, - $this->cache - ); - $actualConfig = $factory->getValidatorConfig(); + $this->readerMock->method('getConfigurationFiles') + ->with('validation.xml') + ->willReturn($this->fileIteratorMock); + $this->fileIteratorMock->method('toArray') + ->willReturn($this->data); + $actualConfig = $this->factory->getValidatorConfig(); $this->assertInstanceOf( \Magento\Framework\Validator\Config::class, $actualConfig, 'Object of incorrect type was created' ); - - // Check that validator translator was correctly instantiated - $validatorTranslator = \Magento\Framework\Validator\AbstractValidator::getDefaultTranslator(); $this->assertInstanceOf( \Magento\Framework\Translate\Adapter::class, - $validatorTranslator, + \Magento\Framework\Validator\AbstractValidator::getDefaultTranslator(), 'Default validator translate adapter was not set correctly' ); } - /** - * Test createValidatorBuilder call - */ + public function testGetValidatorConfigCacheNotExist() + { + $this->cacheMock->expects($this->once()) + ->method('load') + ->willReturn(false); + $this->readerMock->expects($this->once()) + ->method('getConfigurationFiles') + ->willReturn($this->fileIteratorMock); + $this->fileIteratorMock->method('toArray') + ->willReturn($this->data); + $this->cacheMock->expects($this->once()) + ->method('save') + ->with($this->jsonString); + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->with($this->data) + ->willReturn($this->jsonString); + $this->factory->getValidatorConfig(); + $this->factory->getValidatorConfig(); + } + + public function testGetValidatorConfigCacheExist() + { + $this->cacheMock->expects($this->once()) + ->method('load') + ->willReturn($this->jsonString); + $this->readerMock->expects($this->never()) + ->method('getConfigurationFiles'); + $this->cacheMock->expects($this->never()) + ->method('save'); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->with($this->jsonString) + ->willReturn($this->data); + $this->fileIteratorFactoryMock->method('create') + ->willReturn($this->fileIteratorMock); + $this->factory->getValidatorConfig(); + $this->factory->getValidatorConfig(); + } + public function testCreateValidatorBuilder() { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_validatorConfig->expects( - $this->once() - )->method( - 'createValidatorBuilder' - )->with( - 'test', - 'class', - [] - )->will( - $this->returnValue( - $objectManager->getObject(\Magento\Framework\Validator\Builder::class, ['constraints' => []]) - ) - ); - $factory = new \Magento\Framework\Validator\Factory( - $this->_objectManager, - $this->_config, - $this->cache - ); + $this->readerMock->method('getConfigurationFiles') + ->with('validation.xml') + ->willReturn($this->fileIteratorMock); + $this->fileIteratorMock->method('toArray') + ->willReturn($this->data); + $builderMock = $this->getMock(\Magento\Framework\Validator\Builder::class, [], [], '', false); + $this->validatorConfigMock->expects($this->once()) + ->method('createValidatorBuilder') + ->with('test', 'class', []) + ->willReturn($builderMock); $this->assertInstanceOf( \Magento\Framework\Validator\Builder::class, - $factory->createValidatorBuilder('test', 'class', []) + $this->factory->createValidatorBuilder('test', 'class', []) ); } - /** - * Test createValidatorBuilder call - */ public function testCreateValidator() { - $this->_validatorConfig->expects( - $this->once() - )->method( - 'createValidator' - )->with( - 'test', - 'class', - [] - )->will( - $this->returnValue(new \Magento\Framework\Validator()) - ); - $factory = new \Magento\Framework\Validator\Factory( - $this->_objectManager, - $this->_config, - $this->cache + $this->readerMock->method('getConfigurationFiles') + ->with('validation.xml') + ->willReturn($this->fileIteratorMock); + $this->fileIteratorMock->method('toArray') + ->willReturn($this->data); + $validatorMock = $this->getMock(\Magento\Framework\Validator::class, [], [], '', false); + $this->validatorConfigMock->expects($this->once()) + ->method('createValidator') + ->with('test', 'class', []) + ->willReturn($validatorMock); + $this->assertInstanceOf( + \Magento\Framework\Validator::class, + $this->factory->createValidator('test', 'class', []) ); - $this->assertInstanceOf(\Magento\Framework\Validator::class, $factory->createValidator('test', 'class', [])); } } diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/LocaleTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/LocaleTest.php index 673a5616020da..d1da2ba354a61 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/LocaleTest.php +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/LocaleTest.php @@ -1,6 +1,6 @@ validator = new UrlValidator(); + } + + /** + * @param array $allowedSchemes + * @param string $url + * @param bool $expectedResult + * @dataProvider isValidDataProvider + */ + public function testIsValid(array $allowedSchemes, $url, $expectedResult) + { + $this->assertSame($expectedResult, $this->validator->isValid($url, $allowedSchemes)); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + [ + 'allowedSchemes' => [], + 'url' => 'http://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['http'], + 'url' => 'http://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => [], + 'url' => 'https://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['https'], + 'url' => 'https://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => [], + 'url' => 'http://example.com_test', + 'expectedResult' => false, + ], + [ + 'allowedSchemes' => [], + 'url' => 'ftp://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['ftp'], + 'url' => 'ftp://example.com', + 'expectedResult' => true, + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/ValidatorAbstractTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/ValidatorAbstractTest.php index f7e03928fd03d..0f496c9bbae28 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/ValidatorAbstractTest.php +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/ValidatorAbstractTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_builder_instance.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_builder_instance.xml index 9a2c90714d526..9e4a25549084b 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_builder_instance.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_builder_instance.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_child_for_option.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_child_for_option.xml index 9aea586b4bee7..0cc14bf204049 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_child_for_option.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_child_for_option.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_constraint.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_constraint.xml index 57944f0143b2f..4b7b0a24e6772 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_constraint.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_constraint.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_content_for_callback.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_content_for_callback.xml index 3a61e4575ef85..9a07e7bc1bdf2 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_content_for_callback.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_content_for_callback.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_entity_callback.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_entity_callback.xml index ca4bd7fcd527c..8b3ff7a17ee3e 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_entity_callback.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_entity_callback.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method.xml index c8ed7062f0b5a..a13efa48945ca 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method_callback.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method_callback.xml index 2b7267be73a63..14ef83699b01d 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method_callback.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/invalid_method_callback.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/multiple_callback_in_argument.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/multiple_callback_in_argument.xml index 7d9e2a81dbd28..422d097360eef 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/multiple_callback_in_argument.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/multiple_callback_in_argument.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_class_for_constraint.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_class_for_constraint.xml index 86280ce54b062..042932dea0f17 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_class_for_constraint.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_class_for_constraint.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_constraint.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_constraint.xml index 61dcec47f583f..63b15da6656c5 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_constraint.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_constraint.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_entity.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_entity.xml index 94ee985d62511..0b848083efefc 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_entity.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_entity.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_group.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_group.xml index 17c388ee915ab..79121eb16ff65 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_group.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_group.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_rule.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_rule.xml index 483da577d5f9f..82bc468323460 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_rule.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_name_for_rule.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_rule_for_reference.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_rule_for_reference.xml index 812d6f862d839..500e52d20ed65 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_rule_for_reference.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/no_rule_for_reference.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/not_unique_use.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/not_unique_use.xml index 97468d6448e3e..07571c6164b84 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/not_unique_use.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/negative/not_unique_use.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/builder/validation.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/builder/validation.xml index fb4693b450797..9ba4ccdeb2eed 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/builder/validation.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/builder/validation.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_a/validation.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_a/validation.xml index 87348b1c340b6..97b0880fd9b8b 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_a/validation.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_a/validation.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_b/validation.xml b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_b/validation.xml index c060e26469cb1..ba3ef4c988137 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_b/validation.xml +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/_files/validation/positive/module_b/validation.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/Validator/Timezone.php b/lib/internal/Magento/Framework/Validator/Timezone.php index ec221fc308705..0e5f87c7724a9 100644 --- a/lib/internal/Magento/Framework/Validator/Timezone.php +++ b/lib/internal/Magento/Framework/Validator/Timezone.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/ValidatorFactory.php b/lib/internal/Magento/Framework/ValidatorFactory.php index 6508be8bf243f..efdaf300e7917 100644 --- a/lib/internal/Magento/Framework/ValidatorFactory.php +++ b/lib/internal/Magento/Framework/ValidatorFactory.php @@ -1,6 +1,6 @@ area = $areaType; $this->theme = $themePath; $this->locale = $localeCode; - $this->isSecure = $isSecure; parent::__construct($baseUrl, DirectoryList::STATIC_VIEW, $this->generatePath()); } @@ -103,6 +91,6 @@ private function generatePath() */ public function getConfigPath() { - return $this->getPath() . ($this->isSecure ? '/' . self::SECURE_PATH : ''); + return $this->getPath(); } } diff --git a/lib/internal/Magento/Framework/View/Asset/File/FallbackContextFactory.php b/lib/internal/Magento/Framework/View/Asset/File/FallbackContextFactory.php index cd4df0bc49fbf..36fcade1682bb 100644 --- a/lib/internal/Magento/Framework/View/Asset/File/FallbackContextFactory.php +++ b/lib/internal/Magento/Framework/View/Asset/File/FallbackContextFactory.php @@ -1,6 +1,6 @@ filesystem = $filesystem; } + /** + * @deprecated + * @return Source + */ + private function getAssetSource() + { + if (!$this->assetSource) { + $this->assetSource = ObjectManager::getInstance()->get(Source::class); + } + return $this->assetSource; + } + /** * {@inheritdoc} */ public function merge(array $assetsToMerge, \Magento\Framework\View\Asset\LocalInterface $resultAsset) { - $sourceDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT); + $rootDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT); $mTime = null; /** @var \Magento\Framework\View\Asset\MergeableInterface $asset */ foreach ($assetsToMerge as $asset) { - $mTime .= $sourceDir->stat($sourceDir->getRelativePath($asset->getSourceFile()))['mtime']; + $sourceFile = $this->getAssetSource()->findSource($asset); + $mTime .= $rootDir->stat($rootDir->getRelativePath($sourceFile))['mtime']; } + if (null === $mTime) { return; // nothing to merge } diff --git a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php index 68cc706ad9bdf..de9248b8fe10a 100644 --- a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php +++ b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php @@ -1,6 +1,6 @@ composeMergedContent($assetsToMerge, $resultAsset); - $dir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); - $dir->writeFile($resultAsset->getPath(), $mergedContent); + $filePath = $resultAsset->getPath(); + $staticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + $tmpDir = $this->filesystem->getDirectoryWrite(DirectoryList::TMP); + $tmpDir->writeFile($filePath, $mergedContent); + $tmpDir->renameFile($filePath, $filePath, $staticDir); } /** diff --git a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/FileExists.php b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/FileExists.php index 211f3daed3c91..60339fa2d7542 100644 --- a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/FileExists.php +++ b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/FileExists.php @@ -1,6 +1,6 @@ $url, 'areaType' => $area, 'themePath' => $themePath, - 'localeCode' => $locale, - 'isSecure' => $isSecure + 'localeCode' => $locale ] ); } diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php index cf80bd0d1b382..d59f48a136274 100644 --- a/lib/internal/Magento/Framework/View/Asset/Source.php +++ b/lib/internal/Magento/Framework/View/Asset/Source.php @@ -1,6 +1,6 @@ getCode(); + $key = $currentTheme->getFullPath(); if (isset($this->viewConfigs[$key])) { return $this->viewConfigs[$key]; } diff --git a/lib/internal/Magento/Framework/View/ConfigInterface.php b/lib/internal/Magento/Framework/View/ConfigInterface.php index 21cb20bc80815..93ec4ff6869b1 100644 --- a/lib/internal/Magento/Framework/View/ConfigInterface.php +++ b/lib/internal/Magento/Framework/View/ConfigInterface.php @@ -1,6 +1,6 @@ _loadByPath($themeKey, $area); } - if (!$themeModel->getId()) { + if (!$themeModel->getCode()) { throw new \LogicException("Unable to load theme by specified key: '{$themeKey}'"); } $this->_addTheme($themeModel); diff --git a/lib/internal/Magento/Framework/View/Design/Theme/Image.php b/lib/internal/Magento/Framework/View/Design/Theme/Image.php index c6725f07418f9..9093759526cf9 100644 --- a/lib/internal/Magento/Framework/View/Design/Theme/Image.php +++ b/lib/internal/Magento/Framework/View/Design/Theme/Image.php @@ -1,6 +1,6 @@ scopeConfig = $scopeConfig; $this->exceptionConfigPath = $exceptionConfigPath; $this->scopeType = $scopeType; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); } /** @@ -65,7 +78,7 @@ public function getThemeByRequest(\Magento\Framework\App\Request\Http $request) if (!$expressions) { return false; } - $expressions = unserialize($expressions); + $expressions = $this->serializer->unserialize($expressions); foreach ($expressions as $rule) { if (preg_match($rule['regexp'], $userAgent)) { return $rule['value']; diff --git a/lib/internal/Magento/Framework/View/DesignInterface.php b/lib/internal/Magento/Framework/View/DesignInterface.php index e2c0e387dbbb5..6429166932d8e 100644 --- a/lib/internal/Magento/Framework/View/DesignInterface.php +++ b/lib/internal/Magento/Framework/View/DesignInterface.php @@ -1,6 +1,6 @@ _eventManager->dispatch('view_block_abstract_to_html_before', ['block' => $this]); - if ($this->_scopeConfig->getValue( - 'advanced/modules_disable_output/' . $this->getModuleName(), - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )) { - return ''; - } + $this->getModuleName(); $html = $this->_loadCache(); if ($html === false) { @@ -670,6 +665,18 @@ public function toHtml() } $html = $this->_afterToHtml($html); + /** @var \Magento\Framework\DataObject */ + $transportObject = new \Magento\Framework\DataObject( + [ + 'html' => $html, + ] + ); + $this->_eventManager->dispatch('view_block_abstract_to_html_after', [ + 'block' => $this, + 'transport' => $transportObject + ]); + $html = $transportObject->getHtml(); + return $html; } diff --git a/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php b/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php new file mode 100644 index 0000000000000..9fa51aa6e4035 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php @@ -0,0 +1,14 @@ +cache->load(static::CACHE_ID); if ($cachedData === false) { $data = $uiReader->read(); - $this->cache->save(serialize($data), static::CACHE_ID); + $this->cache->save($this->getSerializer()->serialize($data), static::CACHE_ID); } else { - $data = unserialize($cachedData); + $data = $this->getSerializer()->unserialize($cachedData); } $this->prepareComponentData($data); } @@ -109,4 +114,19 @@ protected function prepareComponentData(array $componentsData) $this->setComponentData($name, reset($data)); } } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Provider/Template.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Provider/Template.php index 79d185b2b26a7..c476c650d9b16 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Provider/Template.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Provider/Template.php @@ -1,13 +1,10 @@ aggregatedFileCollector = $aggregatedFileCollector; @@ -81,7 +83,9 @@ public function __construct( $this->aggregatedFileCollectorFactory = $aggregatedFileCollectorFactory; $cachedTemplates = $this->cache->load(static::CACHE_ID); - $this->cachedTemplates = $cachedTemplates === false ? [] : unserialize($cachedTemplates); + $this->cachedTemplates = $cachedTemplates === false ? [] : $this->getSerializer()->unserialize( + $cachedTemplates + ); } /** @@ -104,8 +108,23 @@ public function getTemplate($template) 'domMerger' => $this->domMerger ] )->getContent(); - $this->cache->save(serialize($this->cachedTemplates), static::CACHE_ID); + $this->cache->save($this->getSerializer()->serialize($this->cachedTemplates), static::CACHE_ID); return $this->cachedTemplates[$hash]; } + + /** + * Get serializer + * + * @return \Magento\Framework\Serialize\SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if ($this->serializer === null) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\SerializerInterface::class); + } + return $this->serializer; + } } diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Reader.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Reader.php index e2e8efdee89e2..c8bd5afe5074a 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Reader.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/Reader.php @@ -1,6 +1,6 @@ handles[] = $handle; + } + + /** + * Get list of handles containing entity ID + * + * @return string[] + */ + public function getHandles() + { + return $this->handles; + } +} diff --git a/lib/internal/Magento/Framework/View/File.php b/lib/internal/Magento/Framework/View/File.php index 1751ddc67de51..96c842b03f731 100644 --- a/lib/internal/Magento/Framework/View/File.php +++ b/lib/internal/Magento/Framework/View/File.php @@ -1,6 +1,6 @@ * - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\View\Helper; diff --git a/lib/internal/Magento/Framework/View/Helper/PathPattern.php b/lib/internal/Magento/Framework/View/Helper/PathPattern.php index 7ff51613dbd1a..6d677d77a3200 100644 --- a/lib/internal/Magento/Framework/View/Helper/PathPattern.php +++ b/lib/internal/Magento/Framework/View/Helper/PathPattern.php @@ -1,6 +1,6 @@ _elementClass = \Magento\Framework\View\Layout\Element::class; $this->_renderingOutput = new \Magento\Framework\DataObject(); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); $this->_processorFactory = $processorFactory; $this->_eventManager = $eventManager; @@ -308,12 +318,19 @@ public function generateElements() $cacheId = 'structure_' . $this->getUpdate()->getCacheId(); $result = $this->cache->load($cacheId); if ($result) { - $this->readerContext = unserialize($result); + $data = $this->serializer->unserialize($result); + $this->getReaderContext()->getPageConfigStructure()->populateWithArray($data['pageConfigStructure']); + $this->getReaderContext()->getScheduledStructure()->populateWithArray($data['scheduledStructure']); } else { \Magento\Framework\Profiler::start('build_structure'); $this->readerPool->interpret($this->getReaderContext(), $this->getNode()); \Magento\Framework\Profiler::stop('build_structure'); - $this->cache->save(serialize($this->getReaderContext()), $cacheId, $this->getUpdate()->getHandles()); + + $data = [ + 'pageConfigStructure' => $this->getReaderContext()->getPageConfigStructure()->__toArray(), + 'scheduledStructure' => $this->getReaderContext()->getScheduledStructure()->__toArray(), + ]; + $this->cache->save($this->serializer->serialize($data), $cacheId, $this->getUpdate()->getHandles()); } $generatorContext = $this->generatorContextFactory->create( diff --git a/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php b/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php index ae284734696ad..8b60d1acebe5f 100644 --- a/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php +++ b/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php @@ -1,6 +1,6 @@ getAttribute('remove'), FILTER_VALIDATE_BOOLEAN); if ($elementRemove) { $scheduledStructure->setElementToRemoveList($elementName); - } else { - $data = $scheduledStructure->getStructureElementData($elementName, []); - $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement); - $this->updateScheduledData($currentElement, $data); - $this->evaluateArguments($currentElement, $data); - $scheduledStructure->setStructureElementData($elementName, $data); + return; + } elseif ($currentElement->getAttribute('remove')) { + $scheduledStructure->unsetElementFromListToRemove($elementName); } + $data = $scheduledStructure->getStructureElementData($elementName, []); + $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement); + $this->updateScheduledData($currentElement, $data); + $this->evaluateArguments($currentElement, $data); + $scheduledStructure->setStructureElementData($elementName, $data); } /** diff --git a/lib/internal/Magento/Framework/View/Layout/Reader/Container.php b/lib/internal/Magento/Framework/View/Layout/Reader/Container.php index c4111a13c33b3..21058e26c5473 100755 --- a/lib/internal/Magento/Framework/View/Layout/Reader/Container.php +++ b/lib/internal/Magento/Framework/View/Layout/Reader/Container.php @@ -1,6 +1,6 @@ getAttribute('name'); $containerRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN); - if ($containerRemove) { $scheduledStructure->setElementToRemoveList($containerName); - } else { - $this->mergeContainerAttributes($scheduledStructure, $currentElement); + return; + } elseif ($currentElement->getAttribute('remove')) { + $scheduledStructure->unsetElementFromListToRemove($containerName); } + $this->mergeContainerAttributes($scheduledStructure, $currentElement); } } diff --git a/lib/internal/Magento/Framework/View/Layout/Reader/Context.php b/lib/internal/Magento/Framework/View/Layout/Reader/Context.php index 15ef97b031b1d..423ce6455651f 100644 --- a/lib/internal/Magento/Framework/View/Layout/Reader/Context.php +++ b/lib/internal/Magento/Framework/View/Layout/Reader/Context.php @@ -1,6 +1,6 @@ scheduledStructure = isset($data['scheduledStructure']) ? $data['scheduledStructure'] : []; - $this->scheduledData = isset($data['scheduledData']) ? $data['scheduledData'] : []; - $this->scheduledElements = isset($data['scheduledElements']) ? $data['scheduledElements'] : []; - $this->scheduledMoves = isset($data['scheduledMoves']) ? $data['scheduledMoves'] : []; - $this->scheduledRemoves = isset($data['scheduledRemoves']) ? $data['scheduledRemoves'] : []; - $this->scheduledIfconfig = isset($data['scheduledIfconfig']) ? $data['scheduledIfconfig'] : []; - $this->scheduledPaths = isset($data['scheduledPaths']) ? $data['scheduledPaths'] : []; + $this->populateWithArray($data); } /** @@ -531,4 +540,44 @@ public function flushScheduledStructure() $this->scheduledElements = []; $this->scheduledStructure = []; } + + /** + * Reformat 'Layout scheduled structure' to array. + * + * @return array + */ + public function __toArray() + { + $result = []; + foreach ($this->serializableProperties as $property) { + $result[$property] = $this->{$property}; + } + + return $result; + } + + /** + * Update 'Layout scheduled structure' data. + * + * @param array $data + * @return void + */ + public function populateWithArray(array $data) + { + foreach ($this->serializableProperties as $property) { + $this->{$property} = $this->getArrayValueByKey($property, $data); + } + } + + /** + * Get value from array by key. + * + * @param string $key + * @param array $array + * @return array + */ + private function getArrayValueByKey($key, array $array) + { + return isset($array[$key]) ? $array[$key] : []; + } } diff --git a/lib/internal/Magento/Framework/View/Layout/ScheduledStructure/Helper.php b/lib/internal/Magento/Framework/View/Layout/ScheduledStructure/Helper.php index 870f147a43634..b5b5711cb5f2c 100644 --- a/lib/internal/Magento/Framework/View/Layout/ScheduledStructure/Helper.php +++ b/lib/internal/Magento/Framework/View/Layout/ScheduledStructure/Helper.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 401bf2e93f366..21f27bf343a9d 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -1,7 +1,7 @@ @@ -248,6 +248,7 @@ + diff --git a/lib/internal/Magento/Framework/View/Layout/etc/head.xsd b/lib/internal/Magento/Framework/View/Layout/etc/head.xsd index 7e26ed41a6941..141e42db113cb 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/head.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/head.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/html.xsd b/lib/internal/Magento/Framework/View/Layout/etc/html.xsd index 24a04fe922761..ddc12f3c413d0 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/html.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/html.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd b/lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd index 6624495d3a8ff..b174d3008e93b 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd b/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd index d6faf66fdc094..665b7504255e0 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd b/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd index b77c440ef2e6b..b4b9dcbb43948 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/page_layout.xsd b/lib/internal/Magento/Framework/View/Layout/etc/page_layout.xsd index 7d62f2b903d66..9ea11a18ad23c 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/page_layout.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/page_layout.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Layout/etc/page_types.xsd b/lib/internal/Magento/Framework/View/Layout/etc/page_types.xsd index 9cc119a646827..06088726fd598 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/page_types.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/page_types.xsd @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/LayoutFactory.php b/lib/internal/Magento/Framework/View/LayoutFactory.php index 2890ec5cb8a15..86ac802f654d2 100644 --- a/lib/internal/Magento/Framework/View/LayoutFactory.php +++ b/lib/internal/Magento/Framework/View/LayoutFactory.php @@ -1,6 +1,6 @@ getFileLayoutUpdatesXml(); foreach ($layout->xpath("*[self::handle or self::layout][@id='{$handle}']") as $updateXml) { $this->_fetchRecursiveUpdates($updateXml); - $this->addUpdate($updateXml->innerXml()); + $updateInnerXml = $updateXml->innerXml(); + $this->validateUpdate($handle, $updateInnerXml); + $this->addUpdate($updateInnerXml); } \Magento\Framework\Profiler::stop($_profilerKey); @@ -563,12 +565,31 @@ protected function _fetchDbLayoutUpdates($handle) $updateStr = $this->_substitutePlaceholders($updateStr); $updateXml = $this->_loadXmlString($updateStr); $this->_fetchRecursiveUpdates($updateXml); - $this->addUpdate($updateXml->innerXml()); + $updateInnerXml = $updateXml->innerXml(); + $this->validateUpdate($handle, $updateInnerXml); + $this->addUpdate($updateInnerXml); \Magento\Framework\Profiler::stop($_profilerKey); return (bool)$updateStr; } + /** + * Validate layout update content, throw exception on failure. + * + * This method is used as a hook for plugins. + * + * @param string $handle + * @param string $updateXml + * @return void + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @codeCoverageIgnore + */ + public function validateUpdate($handle, $updateXml) + { + return; + } + /** * Substitute placeholders {{placeholder_name}} with their values in XML string * diff --git a/lib/internal/Magento/Framework/View/Model/Layout/Translator.php b/lib/internal/Magento/Framework/View/Model/Layout/Translator.php index 0cbc336a44fd2..07539d8a3d693 100644 --- a/lib/internal/Magento/Framework/View/Model/Layout/Translator.php +++ b/lib/internal/Magento/Framework/View/Model/Layout/Translator.php @@ -1,6 +1,6 @@ assets; } + + /** + * Reformat 'Page config structure' to array. + * + * @return array + */ + public function __toArray() + { + $result = []; + foreach ($this->serializableProperties as $property) { + $result[$property] = $this->{$property}; + } + + return $result; + } + + /** + * Update 'Page config structure' data. + * + * @param array $data + * @return void + */ + public function populateWithArray(array $data) + { + foreach ($this->serializableProperties as $property) { + $this->{$property} = $this->getArrayValueByKey($property, $data); + } + } + + /** + * Get value from array by key. + * + * @param string $key + * @param array $array + * @return array + */ + private function getArrayValueByKey($key, array $array) + { + return isset($array[$key]) ? $array[$key] : []; + } } diff --git a/lib/internal/Magento/Framework/View/Page/ConfigFactory.php b/lib/internal/Magento/Framework/View/Page/ConfigFactory.php index d0fed31c244d9..c4071567de02f 100644 --- a/lib/internal/Magento/Framework/View/Page/ConfigFactory.php +++ b/lib/internal/Magento/Framework/View/Page/ConfigFactory.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/View/Render/RenderFactory.php b/lib/internal/Magento/Framework/View/Render/RenderFactory.php index 1b96a01e84451..4fc8a7e065e4a 100644 --- a/lib/internal/Magento/Framework/View/Render/RenderFactory.php +++ b/lib/internal/Magento/Framework/View/Render/RenderFactory.php @@ -1,6 +1,6 @@ request = $context->getRequest(); $this->assetRepo = $context->getAssetRepository(); @@ -127,6 +134,8 @@ public function __construct( $this->viewFileSystem = $context->getViewFileSystem(); $this->pageConfigRendererFactory = $pageConfigRendererFactory; $this->template = $template; + $this->entitySpecificHandlesList = $entitySpecificHandlesList + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(View\EntitySpecificHandlesList::class); parent::__construct( $context, $layoutFactory, @@ -205,17 +214,23 @@ public function getConfig() * * @param array|null $parameters page parameters * @param string|null $defaultHandle + * @param bool $entitySpecific * @return bool */ - public function addPageLayoutHandles(array $parameters = [], $defaultHandle = null) + public function addPageLayoutHandles(array $parameters = [], $defaultHandle = null, $entitySpecific = true) { $handle = $defaultHandle ? $defaultHandle : $this->getDefaultLayoutHandle(); $pageHandles = [$handle]; foreach ($parameters as $key => $value) { - $pageHandles[] = $handle . '_' . $key . '_' . $value; + $pageHandle = $handle . '_' . $key . '_' . $value; + $pageHandles[] = $pageHandle; + if ($entitySpecific) { + $this->entitySpecificHandlesList->addHandle($pageHandle); + } } // Do not sort array going into add page handles. Ensure default layout handle is added first. - return $this->addHandle($pageHandles); + $this->addHandle($pageHandles); + return true; } /** diff --git a/lib/internal/Magento/Framework/View/Result/PageFactory.php b/lib/internal/Magento/Framework/View/Result/PageFactory.php index aa35b9d3e24f1..6aba62bf6a41c 100644 --- a/lib/internal/Magento/Framework/View/Result/PageFactory.php +++ b/lib/internal/Magento/Framework/View/Result/PageFactory.php @@ -1,6 +1,6 @@ fallbackContext = $this->objectManager->getObject( @@ -49,8 +47,7 @@ public function testGetConfigPath( 'baseUrl' => $baseUrl, 'areaType' => $areaType, 'themePath' => $themePath, - 'localeCode' => $localeCode, - 'isSecure' => $isSecure + 'localeCode' => $localeCode ] ); $this->assertEquals($expectedResult, $this->fallbackContext->getConfigPath()); @@ -64,7 +61,6 @@ public function getConfigPathDataProvider() 'areaType' => 'frontend', 'themePath' => 'Magento/blank', 'localeCode' => 'en_US', - 'isSecure' => false, 'expectedResult' => 'frontend/Magento/blank/en_US' ], 'https' => [ @@ -72,8 +68,7 @@ public function getConfigPathDataProvider() 'areaType' => 'frontend', 'themePath' => 'Magento/blank', 'localeCode' => 'en_US', - 'isSecure' => true, - 'expectedResult' => 'frontend/Magento/blank/en_US/secure' + 'expectedResult' => 'frontend/Magento/blank/en_US' ] ]; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php index 1ad1ab80fdef4..45c630dcf90c4 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php @@ -1,6 +1,6 @@ with(DirectoryList::STATIC_VIEW) ->will($this->returnValue($this->targetDir)); $this->checksum = new Checksum($this->mergerMock, $filesystem); + $this->assetSource = $this->getMockBuilder(Source::class) + ->disableOriginalConstructor() + ->getMock(); + + $reflection = new \ReflectionClass(Checksum::class); + $reflectionProperty = $reflection->getProperty('assetSource'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->checksum, $this->assetSource); + $this->resultAsset = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false); } @@ -114,9 +129,17 @@ public function testMergeMtimeUnchanged() private function getAssetsToMerge() { $one = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false); - $one->expects($this->once())->method('getSourceFile')->will($this->returnValue('/dir/file/one.txt')); $two = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false); - $two->expects($this->once())->method('getSourceFile')->will($this->returnValue('/dir/file/two.txt')); + $one->expects($this->never()) + ->method('getSourceFile'); + $two->expects($this->never()) + ->method('getSourceFile'); + + $this->assetSource->expects($this->exactly(2)) + ->method('findSource') + ->withConsecutive([$one], [$two]) + ->willReturnOnConsecutiveCalls('/dir/file/one.txt', '/dir/file/two.txt'); + $this->sourceDir->expects($this->exactly(2)) ->method('getRelativePath') ->will($this->onConsecutiveCalls('file/one.txt', 'file/two.txt')); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php index 23041feb2b8c6..33ad9608109e9 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php @@ -1,10 +1,11 @@ cssUrlResolver = $this->getMock(\Magento\Framework\View\Url\CssResolver::class); $filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); - $this->writeDir = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\WriteInterface::class); + $this->staticDir = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); + $this->tmpDir = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); $filesystem->expects($this->any()) ->method('getDirectoryWrite') - ->with(DirectoryList::STATIC_VIEW) - ->will($this->returnValue($this->writeDir)); + ->willReturnMap([ + [DirectoryList::STATIC_VIEW, \Magento\Framework\Filesystem\DriverPool::FILE, $this->staticDir], + [DirectoryList::TMP, \Magento\Framework\Filesystem\DriverPool::FILE, $this->tmpDir], + ]); $this->resultAsset = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false); $this->object = new Direct($filesystem, $this->cssUrlResolver); } @@ -47,7 +56,9 @@ protected function setUp() public function testMergeNoAssets() { $this->resultAsset->expects($this->once())->method('getPath')->will($this->returnValue('foo/result')); - $this->writeDir->expects($this->once())->method('writeFile')->with('foo/result', ''); + $this->staticDir->expects($this->never())->method('writeFile'); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', ''); + $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); $this->object->merge([], $this->resultAsset); } @@ -55,7 +66,9 @@ public function testMergeGeneric() { $this->resultAsset->expects($this->once())->method('getPath')->will($this->returnValue('foo/result')); $assets = $this->prepareAssetsToMerge([' one', 'two']); // note leading space intentionally - $this->writeDir->expects($this->once())->method('writeFile')->with('foo/result', 'onetwo'); + $this->staticDir->expects($this->never())->method('writeFile'); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', 'onetwo'); + $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); $this->object->merge($assets, $this->resultAsset); } @@ -73,7 +86,9 @@ public function testMergeCss() ->method('aggregateImportDirectives') ->with('12') ->will($this->returnValue('1020')); - $this->writeDir->expects($this->once())->method('writeFile')->with('foo/result', '1020'); + $this->staticDir->expects($this->never())->method('writeFile'); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', '1020'); + $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); $this->object->merge($assets, $this->resultAsset); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/FileExistsTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/FileExistsTest.php index 2354411700ce8..d42ad9c988429 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/FileExistsTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/FileExistsTest.php @@ -1,6 +1,6 @@ '', 'areaType' => '', 'themePath' => 'Default', - 'localeCode' => '', - 'isSecure' => '', + 'localeCode' => '' ] ) ->willReturn($fallbackContextMock); @@ -251,8 +250,7 @@ public function testGetStaticViewFileContext() 'baseUrl' => '', 'areaType' => 'area', 'themePath' => '', - 'localeCode' => 'locale', - 'isSecure' => '', + 'localeCode' => 'locale' ] ) ->willReturn($fallbackContextMock); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php index 2a704650aa9dd..fdc14e333e839 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php @@ -1,6 +1,6 @@ getMock( \Magento\Theme\Model\Theme::class, - ['getCode'], + ['getFullPath'], [], '', false ); $themeMock->expects($this->atLeastOnce()) - ->method('getCode') + ->method('getFullPath') ->will($this->returnValue($themeCode)); $params = [ 'themeModel' => $themeMock, diff --git a/lib/internal/Magento/Framework/View/Test/Unit/ContextTest.php b/lib/internal/Magento/Framework/View/Test/Unit/ContextTest.php index 59e5b0e7a43f8..7225fee0c0ef4 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/ContextTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/ContextTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Theme\Model\Theme::class, [], [], '', false); - $theme->expects($this->exactly(3))->method('getId')->will($this->returnValue($expectedId)); + $theme->expects($this->exactly(2))->method('getId')->will($this->returnValue($expectedId)); $theme->expects($this->once())->method('getFullPath')->will($this->returnValue(null)); - + $theme->expects($this->once())->method('getCode')->willReturn($expectedId); $this->themeProviderMock->expects( $this->once() )->method( @@ -70,10 +70,10 @@ public function testCreateByPath() $path = 'frontend/Magento/luma'; $themeId = 7; $theme = $this->getMock(\Magento\Theme\Model\Theme::class, [], [], '', false); - $theme->expects($this->exactly(3))->method('getId')->will($this->returnValue($themeId)); + $theme->expects($this->exactly(2))->method('getId')->will($this->returnValue($themeId)); $theme->expects($this->once())->method('getFullPath')->will($this->returnValue($path)); - + $theme->expects($this->once())->method('getCode')->willReturn('Magento/luma'); $this->themeProviderMock->expects( $this->once() )->method( @@ -95,7 +95,6 @@ public function testCreateDummy() { $themeId = 0; $theme = $this->getMock(\Magento\Theme\Model\Theme::class, [], [], '', false); - $theme->expects($this->once())->method('getId')->will($this->returnValue($themeId)); $this->themeProviderMock->expects( $this->once() diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Design/Theme/Image/UploaderTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Design/Theme/Image/UploaderTest.php index 7a33830bc8cfb..66ceedc030d03 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Design/Theme/Image/UploaderTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Design/Theme/Image/UploaderTest.php @@ -1,6 +1,6 @@ scopeConfigMock = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->requestMock = $this->getMock(\Magento\Framework\App\Request\Http::class, [], [], '', false); + $this->serializerMock = $this->getMock(Json::class, [], [], '', false); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->designExceptions = $this->objectManagerHelper->getObject( @@ -39,32 +44,39 @@ protected function setUp() [ 'scopeConfig' => $this->scopeConfigMock, 'exceptionConfigPath' => $this->exceptionConfigPath, - 'scopeType' => $this->scopeType + 'scopeType' => $this->scopeType, + 'serializer' => $this->serializerMock, ] ); } /** * @param string $userAgent - * @param bool $useConfig + * @param bool $configValue + * @param int $callNum * @param bool|string $result * @param array $expressions * @dataProvider getThemeByRequestDataProvider */ - public function testGetThemeByRequest($userAgent, $useConfig, $result, $expressions = []) + public function testGetThemeByRequest($userAgent, $configValue, $callNum, $result, $expressions = []) { $this->requestMock->expects($this->once()) ->method('getServer') ->with($this->equalTo('HTTP_USER_AGENT')) ->will($this->returnValue($userAgent)); - if ($useConfig) { + if ($userAgent) { $this->scopeConfigMock->expects($this->once()) ->method('getValue') ->with($this->equalTo($this->exceptionConfigPath), $this->equalTo($this->scopeType)) - ->will($this->returnValue(serialize($expressions))); + ->will($this->returnValue($configValue)); } + $this->serializerMock->expects($this->exactly($callNum)) + ->method('unserialize') + ->with($configValue) + ->willReturn($expressions); + $this->assertSame($result, $this->designExceptions->getThemeByRequest($this->requestMock)); } @@ -74,11 +86,11 @@ public function testGetThemeByRequest($userAgent, $useConfig, $result, $expressi public function getThemeByRequestDataProvider() { return [ - [false, false, false], - ['iphone', false, false], - ['iphone', true, false], - ['iphone', true, 'matched', [['regexp' => '/iphone/', 'value' => 'matched']]], - ['explorer', true, false, [['regexp' => '/iphone/', 'value' => 'matched']]], + [false, null, 0, false], + ['iphone', null, 0, false], + ['iphone', 'serializedExpressions1', 1, false], + ['iphone', 'serializedExpressions2', 1, 'matched', [['regexp' => '/iphone/', 'value' => 'matched']]], + ['explorer', 'serializedExpressions3', 1, false, [['regexp' => '/iphone/', 'value' => 'matched']]], ]; } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/DesignLoaderTest.php b/lib/internal/Magento/Framework/View/Test/Unit/DesignLoaderTest.php index 58331e32deda4..9435c1a09e311 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/DesignLoaderTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/DesignLoaderTest.php @@ -1,6 +1,6 @@ block->setData('module_name', $moduleName); - $this->eventManagerMock->expects($this->once()) + $this->eventManagerMock->expects($this->exactly(2)) ->method('dispatch') - ->with('view_block_abstract_to_html_before', ['block' => $this->block]); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE) - ->willReturn(true); + ->willReturnMap([ + ['view_block_abstract_to_html_before', ['block' => $this->block]], + ['view_block_abstract_to_html_after', ['block' => $this->block]], + ]); $this->assertSame('', $this->block->toHtml()); } @@ -219,6 +218,7 @@ public function testToHtmlWhenModuleIsDisabled() * @param string|bool $cacheLifetime * @param string|bool $dataFromCache * @param string $dataForSaveCache + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $expectsDispatchEvent * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $expectsCacheLoad * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $expectsCacheSave * @param string $expectedResult @@ -229,6 +229,7 @@ public function testGetCacheLifetimeViaToHtml( $cacheLifetime, $dataFromCache, $dataForSaveCache, + $expectsDispatchEvent, $expectsCacheLoad, $expectsCacheSave, $expectedResult @@ -239,13 +240,8 @@ public function testGetCacheLifetimeViaToHtml( $this->block->setData('module_name', $moduleName); $this->block->setData('cache_lifetime', $cacheLifetime); - $this->eventManagerMock->expects($this->once()) - ->method('dispatch') - ->with('view_block_abstract_to_html_before', ['block' => $this->block]); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE) - ->willReturn(false); + $this->eventManagerMock->expects($expectsDispatchEvent) + ->method('dispatch'); $this->cacheStateMock->expects($this->any()) ->method('isEnabled') ->with(AbstractBlock::CACHE_GROUP) @@ -278,6 +274,7 @@ public function getCacheLifetimeDataProvider() 'cacheLifetime' => null, 'dataFromCache' => 'dataFromCache', 'dataForSaveCache' => '', + 'expectsDispatchEvent' => $this->exactly(2), 'expectsCacheLoad' => $this->never(), 'expectsCacheSave' => $this->never(), 'expectedResult' => '', @@ -286,6 +283,7 @@ public function getCacheLifetimeDataProvider() 'cacheLifetime' => false, 'dataFromCache' => 'dataFromCache', 'dataForSaveCache' => '', + 'expectsDispatchEvent' => $this->exactly(2), 'expectsCacheLoad' => $this->once(), 'expectsCacheSave' => $this->never(), 'expectedResult' => 'dataFromCache', @@ -294,6 +292,7 @@ public function getCacheLifetimeDataProvider() 'cacheLifetime' => 120, 'dataFromCache' => 'dataFromCache', 'dataForSaveCache' => '', + 'expectsDispatchEvent' => $this->exactly(2), 'expectsCacheLoad' => $this->once(), 'expectsCacheSave' => $this->never(), 'expectedResult' => 'dataFromCache', @@ -302,6 +301,7 @@ public function getCacheLifetimeDataProvider() 'cacheLifetime' => '120string', 'dataFromCache' => 'dataFromCache', 'dataForSaveCache' => '', + 'expectsDispatchEvent' => $this->exactly(2), 'expectsCacheLoad' => $this->once(), 'expectsCacheSave' => $this->never(), 'expectedResult' => 'dataFromCache', @@ -310,6 +310,7 @@ public function getCacheLifetimeDataProvider() 'cacheLifetime' => 120, 'dataFromCache' => false, 'dataForSaveCache' => '', + 'expectsDispatchEvent' => $this->exactly(2), 'expectsCacheLoad' => $this->once(), 'expectsCacheSave' => $this->once(), 'expectedResult' => '', diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/BlockFactoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/BlockFactoryTest.php index c430e8a31f756..19312ced6fad5 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/BlockFactoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/BlockFactoryTest.php @@ -1,6 +1,6 @@ entitySpecificHandlesList = $objectManager->getObject(EntitySpecificHandlesList::class); + } + + public function testAddAndGetHandles() + { + $this->assertEquals([], $this->entitySpecificHandlesList->getHandles()); + $this->entitySpecificHandlesList->addHandle('handle1'); + $this->entitySpecificHandlesList->addHandle('handle2'); + $this->assertEquals(['handle1', 'handle2'], $this->entitySpecificHandlesList->getHandles()); + } +} diff --git a/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php b/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php index 25b3bededf04d..ff6f3c94cadec 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/File/Collector/BaseTest.php @@ -1,6 +1,6 @@ self::EXPECTED_CLASS]; - $this->_objectManager->expects( - $this->once() - )->method( - 'create' - )->with( - self::EXPECTED_CLASS - )->will( - $this->returnValue($this) - ); + $input = ['name' => 'dataSource', 'value' => self::EXPECTED_CLASS]; + $this->_objectManager->expects($this->once()) + ->method('create') + ->with(self::EXPECTED_CLASS) + ->willReturn($this); $actual = $this->_model->evaluate($input); $this->assertSame($this, $actual); @@ -56,17 +51,18 @@ public function testEvaluateWrongClass($input, $expectedException, $expectedExce { $this->setExpectedException($expectedException, $expectedExceptionMessage); $self = $this; - $this->_objectManager->expects($this->any())->method('create')->will( - $this->returnCallback( - function ($className) use ($self) { - return $self->getMock($className); - } - ) + $this->_objectManager->expects($this->any())->method('create')->willReturnCallback( + function ($className) use ($self) { + return $self->getMock($className); + } ); $this->_model->evaluate($input); } + /** + * @return array + */ public function evaluateWrongClassDataProvider() { return [ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/OptionsTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/OptionsTest.php index 87bdef315a5c8..b2a30b5cb1290 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/OptionsTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/OptionsTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/BuilderFactoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/BuilderFactoryTest.php index 6afd0ca712149..bbbc2f252b480 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/BuilderFactoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/BuilderFactoryTest.php @@ -1,6 +1,6 @@ scheduledStructure->expects($this->once()) + ->method('unsetElementFromListToRemove') + ->with($literal); + } + $this->context->expects($this->once())->method('getScheduledStructure') ->will($this->returnValue($this->scheduledStructure)); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php index 4e10a5a6a0841..430ec56230f0b 100755 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php @@ -1,6 +1,6 @@ with($contextMock, $elementCurrent) ->willReturnSelf(); + if ($elementCurrent->getAttribute('remove') == 'false') { + $scheduledStructureMock->expects($this->once()) + ->method('unsetElementFromListToRemove') + ->with($elementCurrent->getAttribute('name')); + } + $this->container->interpret($contextMock, $elementCurrent); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/FactoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/FactoryTest.php index a91ac3b38709d..a6608ad6c053c 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/FactoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/FactoryTest.php @@ -1,6 +1,6 @@ expects($this->once())->method('setStructureElementData')->with( $element->getAttribute('name'), - ['attributes' => ['group' => '', 'component' => 'listing']] + ['attributes' => ['group' => '', 'component' => 'listing', 'acl' => 'test', 'condition' => 'test']] ); $scheduleStructure->expects($this->once())->method('setElementToIfconfigList')->with( $element->getAttribute('name'), @@ -88,7 +88,12 @@ public function interpretDataProvider() return [ [ $this->getElement( - '', + '', 'uiComponent' ), ] diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/ReaderPoolTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/ReaderPoolTest.php index 9dddead0e2af8..440218fb5376f 100755 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/ReaderPoolTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/ReaderPoolTest.php @@ -1,6 +1,6 @@ test test - \ No newline at end of file + diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/arguments.xml b/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/arguments.xml index 3be95e59dfa11..04b102f7aa0df 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/arguments.xml +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/arguments.xml @@ -1,7 +1,7 @@ @@ -33,7 +33,7 @@ Excel XML - Magento\Sales\Model\Order\Grid\Massaction\ItemsUpdater + Magento\Framework\View\Layout\Argument\UpdaterInterface Move to Archive */sales_archive/massAdd diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/invalidLayoutArgumentsXmlArray.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/invalidLayoutArgumentsXmlArray.php index c6ec5526e4033..810692b94fa49 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/invalidLayoutArgumentsXmlArray.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/_files/invalidLayoutArgumentsXmlArray.php @@ -1,6 +1,6 @@ structureMock = $this->getMockBuilder(\Magento\Framework\View\Layout\Data\Structure::class) @@ -141,9 +158,22 @@ protected function setUp() $this->readerContextFactoryMock = $this->getMockBuilder( \Magento\Framework\View\Layout\Reader\ContextFactory::class )->disableOriginalConstructor()->getMock(); + + $this->pageConfigStructure = $this->getMockBuilder(\Magento\Framework\View\Page\Config\Structure::class) + ->setMethods(['__toArray', 'populateWithArray']) + ->getMock(); + $this->layoutScheduledSructure = $this->getMockBuilder(\Magento\Framework\View\Layout\ScheduledStructure::class) + ->setMethods(['__toArray', 'populateWithArray']) + ->getMock(); $this->readerContextMock = $this->getMockBuilder(\Magento\Framework\View\Layout\Reader\Context::class) + ->setMethods(['getPageConfigStructure', 'getScheduledStructure']) ->disableOriginalConstructor() ->getMock(); + $this->readerContextMock->expects($this->any())->method('getPageConfigStructure') + ->willReturn($this->pageConfigStructure); + $this->readerContextMock->expects($this->any())->method('getScheduledStructure') + ->willReturn($this->layoutScheduledSructure); + $this->generatorContextFactoryMock = $this->getMockBuilder( \Magento\Framework\View\Layout\Generator\ContextFactory::class ) @@ -154,6 +184,15 @@ protected function setUp() ->getMock(); $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) ->getMock(); + $this->serializer = $this->getMock(\Magento\Framework\Serialize\SerializerInterface::class); + $this->serializer->expects($this->any())->method('serialize') + ->willReturnCallback(function ($value) { + return json_encode($value); + }); + $this->serializer->expects($this->any())->method('unserialize') + ->willReturnCallback(function ($value) { + return json_decode($value, true); + }); $this->model = new \Magento\Framework\View\Layout( $this->processorFactoryMock, @@ -168,7 +207,8 @@ protected function setUp() $this->generatorContextFactoryMock, $this->appStateMock, $this->loggerMock, - true + true, + $this->serializer ); } @@ -735,9 +775,32 @@ public function testGenerateElementsWithoutCache() ->with($this->readerContextMock, $xml) ->willReturnSelf(); + $pageConfigStructureData = [ + 'field_1' => 123, + 'field_2' => 'text', + 'field_3' => [ + 'field_3_1' => '1244', + 'field_3_2' => null, + 'field_3_3' => false, + ] + ]; + $this->pageConfigStructure->expects($this->any())->method('__toArray') + ->willReturn($pageConfigStructureData); + + $layoutScheduledStructureData = [ + 'field_1' => 1283, + 'field_2' => 'text_qwertyuiop[]asdfghjkl;' + ]; + $this->layoutScheduledSructure->expects($this->any())->method('__toArray') + ->willReturn($layoutScheduledStructureData); + $data = [ + 'pageConfigStructure' => $pageConfigStructureData, + 'scheduledStructure' => $layoutScheduledStructureData + ]; + $this->cacheMock->expects($this->once()) ->method('save') - ->with(serialize($this->readerContextMock), 'structure_' . $layoutCacheId, $handles) + ->with(json_encode($data), 'structure_' . $layoutCacheId, $handles) ->willReturn(true); $generatorContextMock = $this->getMockBuilder(\Magento\Framework\View\Layout\Generator\Context::class) @@ -774,6 +837,9 @@ public function testGenerateElementsWithCache() $xml = simplexml_load_string('', \Magento\Framework\View\Layout\Element::class); $this->model->setXml($xml); + $this->readerContextFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->readerContextMock); $themeMock = $this->getMockForAbstractClass(\Magento\Framework\View\Design\ThemeInterface::class); $this->themeResolverMock->expects($this->once()) ->method('get') @@ -787,14 +853,33 @@ public function testGenerateElementsWithCache() ->method('getCacheId') ->willReturn($layoutCacheId); - $readerContextMock = $this->getMockBuilder(\Magento\Framework\View\Layout\Reader\Context::class) - ->disableOriginalConstructor() - ->getMock(); + $pageConfigStructureData = [ + 'field_1' => 123, + 'field_2' => 'text', + 'field_3' => [ + 'field_3_1' => '1244', + 'field_3_2' => null, + 'field_3_3' => false, + ] + ]; + $this->pageConfigStructure->expects($this->once())->method('populateWithArray') + ->with($pageConfigStructureData); + + $layoutScheduledStructureData = [ + 'field_1' => 1283, + 'field_2' => 'text_qwertyuiop[]asdfghjkl;' + ]; + $this->layoutScheduledSructure->expects($this->once())->method('populateWithArray') + ->with($layoutScheduledStructureData); + $data = [ + 'pageConfigStructure' => $pageConfigStructureData, + 'scheduledStructure' => $layoutScheduledStructureData + ]; $this->cacheMock->expects($this->once()) ->method('load') ->with('structure_' . $layoutCacheId) - ->willReturn(serialize($readerContextMock)); + ->willReturn(json_encode($data)); $this->readerPoolMock->expects($this->never()) ->method('interpret'); @@ -811,7 +896,7 @@ public function testGenerateElementsWithCache() $this->generatorPoolMock->expects($this->once()) ->method('process') - ->with($readerContextMock, $generatorContextMock) + ->with($this->readerContextMock, $generatorContextMock) ->willReturn(true); $elements = [ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Model/Layout/MergeTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Model/Layout/MergeTest.php index 561c48b915de3..48fdff2fe0960 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Model/Layout/MergeTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Model/Layout/MergeTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_head.xml b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_head.xml index 9ed7ae97f18e7..27c6a73879a0f 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_head.xml +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_head.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_html.xml b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_html.xml index b6e011039ff89..35c4306ee74a5 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_html.xml +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/_files/template_html.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index 32f319f51c732..9ae9241e739f8 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -1,6 +1,6 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/PageLayout/_files/layouts_two.xml b/lib/internal/Magento/Framework/View/Test/Unit/PageLayout/_files/layouts_two.xml index 285dd37a4c2b4..6103558052070 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/PageLayout/_files/layouts_two.xml +++ b/lib/internal/Magento/Framework/View/Test/Unit/PageLayout/_files/layouts_two.xml @@ -1,7 +1,7 @@ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Render/RenderFactoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Render/RenderFactoryTest.php index 0ae4cf420a4ec..ea88eb9f10889 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Render/RenderFactoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Render/RenderFactoryTest.php @@ -1,6 +1,6 @@ with(['pageConfig' => $this->pageConfig]) ->willReturn($this->pageConfigRenderer); + $this->entitySpecificHandlesListMock = $this->getMock(EntitySpecificHandlesList::class, [], [], '', false); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->page = $objectManagerHelper->getObject( \Magento\Framework\View\Result\Page::class, @@ -128,6 +136,7 @@ protected function setUp() 'context' => $this->context, 'translateInline' => $this->translateInline, 'pageConfigRendererFactory' => $pageConfigRendererFactory, + 'entitySpecificHandlesList' => $this->entitySpecificHandlesListMock ] ); } @@ -221,9 +230,41 @@ public function testAddPageLayoutHandles() ->with($expected) ->willReturnSelf(); + $this->entitySpecificHandlesListMock->expects($this->at(0)) + ->method('addHandle')->with('full_action_name_key_one_val_one'); + $this->entitySpecificHandlesListMock->expects($this->at(1)) + ->method('addHandle')->with('full_action_name_key_two_val_two'); + $this->page->addPageLayoutHandles($parameters, $defaultHandle); } + public function testAddPageLayoutHandlesNotEntitySpecific() + { + $fullActionName = 'Full_Action_Name'; + $defaultHandle = null; + $parameters = [ + 'key_one' => 'val_one', + 'key_two' => 'val_two', + ]; + $expected = [ + 'full_action_name', + 'full_action_name_key_one_val_one', + 'full_action_name_key_two_val_two', + ]; + $this->request->expects($this->any()) + ->method('getFullActionName') + ->will($this->returnValue($fullActionName)); + + $this->layoutMerge->expects($this->any()) + ->method('addHandle') + ->with($expected) + ->willReturnSelf(); + + $this->entitySpecificHandlesListMock->expects($this->never())->method('addHandle'); + + $this->page->addPageLayoutHandles($parameters, $defaultHandle, false); + } + public function testAddPageLayoutHandlesWithDefaultHandle() { $defaultHandle = 'default_handle'; diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php index fb921f3776b9b..0ab955a06f896 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php @@ -1,6 +1,6 @@ @@ -160,7 +160,7 @@ public function testMinify() TEXT; $expectedContent = << Test titleText Link some textsomeMethod(); ?>
    +
    + Snippet: +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    DirectiveHowSourceRendered
    ng-bind-htmlAutomatically uses $sanitize
    <div ng-bind-html="snippet">
    </div>
    ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
    <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
    +     </div>
    +
    ng-bindAutomatically escapes
    <div ng-bind="snippet">
    </div>
    +
    + + + it('should sanitize the html snippet by default', function() { + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('

    an html\nclick here\nsnippet

    '); + }); + it('should inline raw snippet if bound to a trusted value', function() { + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). + toBe("

    an html\n" + + "click here\n" + + "snippet

    "); + }); + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new text'); + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('new text'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new text'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
    + + */ + function $SanitizeProvider() { + this.$get = ['$$sanitizeUri', function($$sanitizeUri) { + return function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { + return !/^unsafe/.test($$sanitizeUri(uri, isImage)); + })); + return buf.join(''); + }; + }]; + } + + function sanitizeText(chars) { + var buf = []; + var writer = htmlSanitizeWriter(buf, angular.noop); + writer.chars(chars); + return buf.join(''); + } + + +// Regular Expressions for parsing tags and attributes + var START_TAG_REGEXP = + /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //g, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements + var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags + var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 + var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 + var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + + +// Special Elements (can contain anything) + var specialElements = makeMap("script,style"); + + var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements); + +//Attributes that have href and hence need to be sanitized + var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); + var validAttrs = angular.extend({}, uriAttrs, makeMap( + 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ + 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+ + 'valign,value,vspace,width')); + + function makeMap(str) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) obj[items[i]] = true; + return obj; + } + + + /** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ + function htmlParser( html, handler ) { + var index, chars, match, stack = [], last = html; + stack.last = function() { return stack[ stack.length - 1 ]; }; + + while ( html ) { + chars = true; + + // Make sure we're not in a script or style element + if ( !stack.last() || !specialElements[ stack.last() ] ) { + + // Comment + if ( html.indexOf("", index) === index) { + if (handler.comment) handler.comment( html.substring( 4, index ) ); + html = html.substring( index + 3 ); + chars = false; + } + // DOCTYPE + } else if ( DOCTYPE_REGEXP.test(html) ) { + match = html.match( DOCTYPE_REGEXP ); + + if ( match ) { + html = html.replace( match[0] , ''); + chars = false; + } + // end tag + } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { + match = html.match( END_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( END_TAG_REGEXP, parseEndTag ); + chars = false; + } + + // start tag + } else if ( BEGIN_TAG_REGEXP.test(html) ) { + match = html.match( START_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( START_TAG_REGEXP, parseStartTag ); + chars = false; + } + } + + if ( chars ) { + index = html.indexOf("<"); + + var text = index < 0 ? html : html.substring( 0, index ); + html = index < 0 ? "" : html.substring( index ); + + if (handler.chars) handler.chars( decodeEntities(text) ); + } + + } else { + html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text){ + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars( decodeEntities(text) ); + + return ""; + }); + + parseEndTag( "", stack.last() ); + } + + if ( html == last ) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag( tag, tagName, rest, unary ) { + tagName = angular.lowercase(tagName); + if ( blockElements[ tagName ] ) { + while ( stack.last() && inlineElements[ stack.last() ] ) { + parseEndTag( "", stack.last() ); + } + } + + if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { + parseEndTag( "", tagName ); + } + + unary = voidElements[ tagName ] || !!unary; + + if ( !unary ) + stack.push( tagName ); + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start( tagName, attrs, unary ); + } + + function parseEndTag( tag, tagName ) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if ( tagName ) + // Find the closest opened tag of the same type + for ( pos = stack.length - 1; pos >= 0; pos-- ) + if ( stack[ pos ] == tagName ) + break; + + if ( pos >= 0 ) { + // Close all the open elements, up the stack + for ( i = stack.length - 1; i >= pos; i-- ) + if (handler.end) handler.end( stack[ i ] ); + + // Remove the open elements from the stack + stack.length = pos; + } + } + } + + var hiddenPre=document.createElement("pre"); + var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; + /** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ + function decodeEntities(value) { + if (!value) { return ''; } + + // Note: IE8 does not preserve spaces at the start/end of innerHTML + // so we must capture them and reattach them afterward + var parts = spaceRe.exec(value); + var spaceBefore = parts[1]; + var spaceAfter = parts[3]; + var content = parts[2]; + if (content) { + hiddenPre.innerHTML=content.replace(//g, '>'); + } + + /** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ + function htmlSanitizeWriter(buf, uriValidator){ + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary){ + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key){ + var lkey=angular.lowercase(key); + var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); + if (validAttrs[lkey] === true && + (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag){ + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars){ + if (!ignore) { + out(encodeEntities(chars)); + } + } + }; + } + + +// define ngSanitize module and register $sanitize service + angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + + /* global sanitizeText: false */ + + /** + * @ngdoc filter + * @name linky + * @function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
    + Snippet: + + + + + + + + + + + + + + + + + + + + + +
    FilterSourceRendered
    linky filter +
    <div ng-bind-html="snippet | linky">
    </div>
    +
    +
    +
    linky target +
    <div ng-bind-html="snippetWithTarget | linky:'_blank'">
    </div>
    +
    +
    +
    no filter
    <div ng-bind="snippet">
    </div>
    + + + it('should linkify the snippet with urls', function() { + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); + }); + it('should not linkify snippet without the linky filter', function() { + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); + }); + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new http://link.'); + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('new http://link.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) + .toBe('new http://link.'); + }); + it('should work with the target property', function() { + expect(element(by.id('linky-target')). + element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). + toBe('http://angularjs.org/'); + expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); + }); + + + */ + angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, + MAILTO_REGEXP = /^mailto:/; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + var url; + var i; + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/mailto then assume mailto + if (match[2] == match[3]) url = 'mailto:' + url; + i = match.index; + addText(raw.substr(0, i)); + addLink(url, match[0].replace(MAILTO_REGEXP, '')); + raw = raw.substring(i + match[0].length); + } + addText(raw); + return $sanitize(html.join('')); + + function addText(text) { + if (!text) { + return; + } + html.push(sanitizeText(text)); + } + + function addLink(url, text) { + html.push(''); + addText(text); + html.push(''); + } + }; + }]); + + +})(window, window.angular); \ No newline at end of file diff --git a/setup/pub/angular-sanitize/angular-sanitize.min.js.map b/setup/pub/angular-sanitize/angular-sanitize.min.js.map new file mode 100644 index 0000000000000..0c993a6ed2afb --- /dev/null +++ b/setup/pub/angular-sanitize/angular-sanitize.min.js.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"angular-sanitize.min.js", +"lineCount":13, +"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAiJtCC,QAASA,EAAY,CAACC,CAAD,CAAQ,CAC3B,IAAIC,EAAM,EACGC,EAAAC,CAAmBF,CAAnBE,CAAwBN,CAAAO,KAAxBD,CACbH,MAAA,CAAaA,CAAb,CACA,OAAOC,EAAAI,KAAA,CAAS,EAAT,CAJoB,CAmE7BC,QAASA,EAAO,CAACC,CAAD,CAAM,CAAA,IAChBC,EAAM,EAAIC,EAAAA,CAAQF,CAAAG,MAAA,CAAU,GAAV,CAAtB,KAAsCC,CACtC,KAAKA,CAAL,CAAS,CAAT,CAAYA,CAAZ,CAAgBF,CAAAG,OAAhB,CAA8BD,CAAA,EAA9B,CAAmCH,CAAA,CAAIC,CAAA,CAAME,CAAN,CAAJ,CAAA,CAAgB,CAAA,CACnD,OAAOH,EAHa,CAmBtBK,QAASA,EAAU,CAAEC,CAAF,CAAQC,CAAR,CAAkB,CAiFnCC,QAASA,EAAa,CAAEC,CAAF,CAAOC,CAAP,CAAgBC,CAAhB,CAAsBC,CAAtB,CAA8B,CAClDF,CAAA,CAAUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,IAAKI,CAAA,CAAeJ,CAAf,CAAL,CACE,IAAA,CAAQK,CAAAC,KAAA,EAAR,EAAwBC,CAAA,CAAgBF,CAAAC,KAAA,EAAhB,CAAxB,CAAA,CACEE,CAAA,CAAa,EAAb,CAAiBH,CAAAC,KAAA,EAAjB,CAICG,EAAA,CAAwBT,CAAxB,CAAL,EAA0CK,CAAAC,KAAA,EAA1C,EAA0DN,CAA1D,EACEQ,CAAA,CAAa,EAAb,CAAiBR,CAAjB,CAKF,EAFAE,CAEA,CAFQQ,CAAA,CAAcV,CAAd,CAER,EAFmC,CAAC,CAACE,CAErC,GACEG,CAAAM,KAAA,CAAYX,CAAZ,CAEF,KAAIY,EAAQ,EAEZX,EAAAY,QAAA,CAAaC,CAAb,CACE,QAAQ,CAACC,CAAD,CAAQC,CAAR,CAAcC,CAAd,CAAiCC,CAAjC,CAAoDC,CAApD,CAAmE,CAMzEP,CAAA,CAAMI,CAAN,CAAA,CAAcI,CAAA,CALFH,CAKE,EAJTC,CAIS,EAHTC,CAGS,EAFT,EAES,CAN2D,CAD7E,CASItB,EAAAwB,MAAJ,EAAmBxB,CAAAwB,MAAA,CAAerB,CAAf,CAAwBY,CAAxB,CAA+BV,CAA/B,CA5B+B,CA+BpDM,QAASA,EAAW,CAAET,CAAF,CAAOC,CAAP,CAAiB,CAAA,IAC/BsB,EAAM,CADyB,CACtB7B,CAEb,IADAO,CACA,CADUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,CAEE,IAAMsB,CAAN,CAAYjB,CAAAX,OAAZ,CAA2B,CAA3B,CAAqC,CAArC,EAA8B4B,CAA9B,EACOjB,CAAA,CAAOiB,CAAP,CADP,EACuBtB,CADvB,CAAwCsB,CAAA,EAAxC;AAIF,GAAY,CAAZ,EAAKA,CAAL,CAAgB,CAEd,IAAM7B,CAAN,CAAUY,CAAAX,OAAV,CAAyB,CAAzB,CAA4BD,CAA5B,EAAiC6B,CAAjC,CAAsC7B,CAAA,EAAtC,CACMI,CAAA0B,IAAJ,EAAiB1B,CAAA0B,IAAA,CAAalB,CAAA,CAAOZ,CAAP,CAAb,CAGnBY,EAAAX,OAAA,CAAe4B,CAND,CATmB,CAhHF,IAC/BE,CAD+B,CACxB1C,CADwB,CACVuB,EAAQ,EADE,CACEC,EAAOV,CAG5C,KAFAS,CAAAC,KAEA,CAFamB,QAAQ,EAAG,CAAE,MAAOpB,EAAA,CAAOA,CAAAX,OAAP,CAAsB,CAAtB,CAAT,CAExB,CAAQE,CAAR,CAAA,CAAe,CACbd,CAAA,CAAQ,CAAA,CAGR,IAAMuB,CAAAC,KAAA,EAAN,EAAuBoB,CAAA,CAAiBrB,CAAAC,KAAA,EAAjB,CAAvB,CAmDEV,CASA,CATOA,CAAAiB,QAAA,CAAiBc,MAAJ,CAAW,kBAAX,CAAgCtB,CAAAC,KAAA,EAAhC,CAA+C,QAA/C,CAAyD,GAAzD,CAAb,CACL,QAAQ,CAACsB,CAAD,CAAMC,CAAN,CAAW,CACjBA,CAAA,CAAOA,CAAAhB,QAAA,CAAaiB,CAAb,CAA6B,IAA7B,CAAAjB,QAAA,CAA2CkB,CAA3C,CAAyD,IAAzD,CAEHlC,EAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAesC,CAAA,CAAeS,CAAf,CAAf,CAEnB,OAAO,EALU,CADd,CASP,CAAArB,CAAA,CAAa,EAAb,CAAiBH,CAAAC,KAAA,EAAjB,CA5DF,KAAyD,CAGvD,GAA8B,CAA9B,GAAKV,CAAAoC,QAAA,CAAa,SAAb,CAAL,CAEER,CAEA,CAFQ5B,CAAAoC,QAAA,CAAa,IAAb,CAAmB,CAAnB,CAER,CAAc,CAAd,EAAKR,CAAL,EAAmB5B,CAAAqC,YAAA,CAAiB,QAAjB,CAAwBT,CAAxB,CAAnB,GAAsDA,CAAtD,GACM3B,CAAAqC,QAEJ,EAFqBrC,CAAAqC,QAAA,CAAiBtC,CAAAuC,UAAA,CAAgB,CAAhB,CAAmBX,CAAnB,CAAjB,CAErB,CADA5B,CACA,CADOA,CAAAuC,UAAA,CAAgBX,CAAhB,CAAwB,CAAxB,CACP,CAAA1C,CAAA,CAAQ,CAAA,CAHV,CAJF,KAUO,IAAKsD,CAAAC,KAAA,CAAoBzC,CAApB,CAAL,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAYqB,CAAZ,CAER,CACExC,CACA;AADOA,CAAAiB,QAAA,CAAcE,CAAA,CAAM,CAAN,CAAd,CAAyB,EAAzB,CACP,CAAAjC,CAAA,CAAQ,CAAA,CAFV,CAHK,IAQA,IAAKwD,CAAAD,KAAA,CAA4BzC,CAA5B,CAAL,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAYwB,CAAZ,CAER,CACE3C,CAEA,CAFOA,CAAAuC,UAAA,CAAgBpB,CAAA,CAAM,CAAN,CAAArB,OAAhB,CAEP,CADAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAkB0B,CAAlB,CAAkC/B,CAAlC,CACA,CAAA1B,CAAA,CAAQ,CAAA,CAHV,CAHK,IAUK0D,EAAAH,KAAA,CAAsBzC,CAAtB,CAAL,GACLmB,CADK,CACGnB,CAAAmB,MAAA,CAAY0B,CAAZ,CADH,IAIH7C,CAEA,CAFOA,CAAAuC,UAAA,CAAgBpB,CAAA,CAAM,CAAN,CAAArB,OAAhB,CAEP,CADAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAkB4B,CAAlB,CAAoC3C,CAApC,CACA,CAAAhB,CAAA,CAAQ,CAAA,CANL,CAUFA,EAAL,GACE0C,CAKA,CALQ5B,CAAAoC,QAAA,CAAa,GAAb,CAKR,CAHIH,CAGJ,CAHmB,CAAR,CAAAL,CAAA,CAAY5B,CAAZ,CAAmBA,CAAAuC,UAAA,CAAgB,CAAhB,CAAmBX,CAAnB,CAG9B,CAFA5B,CAEA,CAFe,CAAR,CAAA4B,CAAA,CAAY,EAAZ,CAAiB5B,CAAAuC,UAAA,CAAgBX,CAAhB,CAExB,CAAI3B,CAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAesC,CAAA,CAAeS,CAAf,CAAf,CANrB,CAzCuD,CA+DzD,GAAKjC,CAAL,EAAaU,CAAb,CACE,KAAMoC,EAAA,CAAgB,UAAhB,CAC4C9C,CAD5C,CAAN,CAGFU,CAAA,CAAOV,CAvEM,CA2EfY,CAAA,EA/EmC,CA2IrCY,QAASA,EAAc,CAACuB,CAAD,CAAQ,CAC7B,GAAI,CAACA,CAAL,CAAc,MAAO,EAIrB,KAAIC,EAAQC,CAAAC,KAAA,CAAaH,CAAb,CACRI,EAAAA,CAAcH,CAAA,CAAM,CAAN,CAClB,KAAII,EAAaJ,CAAA,CAAM,CAAN,CAEjB,IADIK,CACJ,CADcL,CAAA,CAAM,CAAN,CACd,CACEM,CAAAC,UAKA,CALoBF,CAAApC,QAAA,CAAgB,IAAhB,CAAqB,MAArB,CAKpB,CAAAoC,CAAA,CAAU,aAAA,EAAiBC,EAAjB,CACRA,CAAAE,YADQ,CACgBF,CAAAG,UAE5B,OAAON,EAAP,CAAqBE,CAArB,CAA+BD,CAlBF,CA4B/BM,QAASA,EAAc,CAACX,CAAD,CAAQ,CAC7B,MAAOA,EAAA9B,QAAA,CACG,IADH;AACS,OADT,CAAAA,QAAA,CAEG0C,CAFH,CAE4B,QAAQ,CAACZ,CAAD,CAAO,CAC9C,MAAO,IAAP,CAAcA,CAAAa,WAAA,CAAiB,CAAjB,CAAd,CAAoC,GADU,CAF3C,CAAA3C,QAAA,CAKG,IALH,CAKS,MALT,CAAAA,QAAA,CAMG,IANH,CAMS,MANT,CADsB,CAoB/B7B,QAASA,EAAkB,CAACD,CAAD,CAAM0E,CAAN,CAAmB,CAC5C,IAAIC,EAAS,CAAA,CAAb,CACIC,EAAMhF,CAAAiF,KAAA,CAAa7E,CAAb,CAAkBA,CAAA4B,KAAlB,CACV,OAAO,OACEU,QAAQ,CAACtB,CAAD,CAAMa,CAAN,CAAaV,CAAb,CAAmB,CAChCH,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACD2D,EAAAA,CAAL,EAAehC,CAAA,CAAgB3B,CAAhB,CAAf,GACE2D,CADF,CACW3D,CADX,CAGK2D,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAc9D,CAAd,CAAf,GACE4D,CAAA,CAAI,GAAJ,CAcA,CAbAA,CAAA,CAAI5D,CAAJ,CAaA,CAZApB,CAAAmF,QAAA,CAAgBlD,CAAhB,CAAuB,QAAQ,CAAC+B,CAAD,CAAQoB,CAAR,CAAY,CACzC,IAAIC,EAAKrF,CAAAwB,UAAA,CAAkB4D,CAAlB,CAAT,CACIE,EAAmB,KAAnBA,GAAWlE,CAAXkE,EAAqC,KAArCA,GAA4BD,CAA5BC,EAAyD,YAAzDA,GAAgDD,CAC3B,EAAA,CAAzB,GAAIE,CAAA,CAAWF,CAAX,CAAJ,EACsB,CAAA,CADtB,GACGG,CAAA,CAASH,CAAT,CADH,EAC8B,CAAAP,CAAA,CAAad,CAAb,CAAoBsB,CAApB,CAD9B,GAEEN,CAAA,CAAI,GAAJ,CAIA,CAHAA,CAAA,CAAII,CAAJ,CAGA,CAFAJ,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIL,CAAA,CAAeX,CAAf,CAAJ,CACA,CAAAgB,CAAA,CAAI,GAAJ,CANF,CAHyC,CAA3C,CAYA,CAAAA,CAAA,CAAIzD,CAAA,CAAQ,IAAR,CAAe,GAAnB,CAfF,CALgC,CAD7B,KAwBAqB,QAAQ,CAACxB,CAAD,CAAK,CACdA,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACD2D,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAc9D,CAAd,CAAf,GACE4D,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAI5D,CAAJ,CACA,CAAA4D,CAAA,CAAI,GAAJ,CAHF,CAKI5D,EAAJ,EAAW2D,CAAX,GACEA,CADF,CACW,CAAA,CADX,CAPc,CAxBb,OAmCE5E,QAAQ,CAACA,CAAD,CAAO,CACb4E,CAAL;AACEC,CAAA,CAAIL,CAAA,CAAexE,CAAf,CAAJ,CAFgB,CAnCjB,CAHqC,CAha9C,IAAI4D,EAAkB/D,CAAAyF,SAAA,CAAiB,WAAjB,CAAtB,CAwJI3B,EACG,4FAzJP,CA0JEF,EAAiB,2BA1JnB,CA2JEzB,EAAc,yEA3JhB,CA4JE0B,EAAmB,IA5JrB,CA6JEF,EAAyB,SA7J3B,CA8JER,EAAiB,qBA9JnB,CA+JEM,EAAiB,qBA/JnB,CAgKEL,EAAe,yBAhKjB,CAkKEwB,EAA0B,gBAlK5B,CA2KI7C,EAAetB,CAAA,CAAQ,wBAAR,CAIfiF,EAAAA,CAA8BjF,CAAA,CAAQ,gDAAR,CAC9BkF,EAAAA,CAA+BlF,CAAA,CAAQ,OAAR,CADnC,KAEIqB,EAAyB9B,CAAA4F,OAAA,CAAe,EAAf,CACeD,CADf,CAEeD,CAFf,CAF7B,CAOIjE,EAAgBzB,CAAA4F,OAAA,CAAe,EAAf,CAAmBF,CAAnB,CAAgDjF,CAAA,CAAQ,4KAAR,CAAhD,CAPpB;AAYImB,EAAiB5B,CAAA4F,OAAA,CAAe,EAAf,CAAmBD,CAAnB,CAAiDlF,CAAA,CAAQ,2JAAR,CAAjD,CAZrB,CAkBIsC,EAAkBtC,CAAA,CAAQ,cAAR,CAlBtB,CAoBIyE,EAAgBlF,CAAA4F,OAAA,CAAe,EAAf,CACe7D,CADf,CAEeN,CAFf,CAGeG,CAHf,CAIeE,CAJf,CApBpB,CA2BI0D,EAAW/E,CAAA,CAAQ,0CAAR,CA3Bf,CA4BI8E,EAAavF,CAAA4F,OAAA,CAAe,EAAf,CAAmBJ,CAAnB,CAA6B/E,CAAA,CAC1C,ySAD0C,CAA7B,CA5BjB;AA0LI8D,EAAUsB,QAAAC,cAAA,CAAuB,KAAvB,CA1Ld,CA2LI5B,EAAU,wBAsGdlE,EAAA+F,OAAA,CAAe,YAAf,CAA6B,EAA7B,CAAAC,SAAA,CAA0C,WAA1C,CA7UAC,QAA0B,EAAG,CAC3B,IAAAC,KAAA,CAAY,CAAC,eAAD,CAAkB,QAAQ,CAACC,CAAD,CAAgB,CACpD,MAAO,SAAQ,CAAClF,CAAD,CAAO,CACpB,IAAIb,EAAM,EACVY,EAAA,CAAWC,CAAX,CAAiBZ,CAAA,CAAmBD,CAAnB,CAAwB,QAAQ,CAACgG,CAAD,CAAMd,CAAN,CAAe,CAC9D,MAAO,CAAC,SAAA5B,KAAA,CAAeyC,CAAA,CAAcC,CAAd,CAAmBd,CAAnB,CAAf,CADsD,CAA/C,CAAjB,CAGA,OAAOlF,EAAAI,KAAA,CAAS,EAAT,CALa,CAD8B,CAA1C,CADe,CA6U7B,CAuGAR,EAAA+F,OAAA,CAAe,YAAf,CAAAM,OAAA,CAAoC,OAApC,CAA6C,CAAC,WAAD,CAAc,QAAQ,CAACC,CAAD,CAAY,CAAA,IACzEC,EACE,mEAFuE,CAGzEC,EAAgB,UAEpB,OAAO,SAAQ,CAACtD,CAAD,CAAOuD,CAAP,CAAe,CAoB5BC,QAASA,EAAO,CAACxD,CAAD,CAAO,CAChBA,CAAL,EAGAjC,CAAAe,KAAA,CAAU9B,CAAA,CAAagD,CAAb,CAAV,CAJqB,CAOvByD,QAASA,EAAO,CAACC,CAAD,CAAM1D,CAAN,CAAY,CAC1BjC,CAAAe,KAAA,CAAU,KAAV,CACIhC,EAAA6G,UAAA,CAAkBJ,CAAlB,CAAJ;CACExF,CAAAe,KAAA,CAAU,UAAV,CAEA,CADAf,CAAAe,KAAA,CAAUyE,CAAV,CACA,CAAAxF,CAAAe,KAAA,CAAU,IAAV,CAHF,CAKAf,EAAAe,KAAA,CAAU,QAAV,CACAf,EAAAe,KAAA,CAAU4E,CAAV,CACA3F,EAAAe,KAAA,CAAU,IAAV,CACA0E,EAAA,CAAQxD,CAAR,CACAjC,EAAAe,KAAA,CAAU,MAAV,CAX0B,CA1B5B,GAAI,CAACkB,CAAL,CAAW,MAAOA,EAMlB,KALA,IAAId,CAAJ,CACI0E,EAAM5D,CADV,CAEIjC,EAAO,EAFX,CAGI2F,CAHJ,CAII9F,CACJ,CAAQsB,CAAR,CAAgB0E,CAAA1E,MAAA,CAAUmE,CAAV,CAAhB,CAAA,CAEEK,CAMA,CANMxE,CAAA,CAAM,CAAN,CAMN,CAJIA,CAAA,CAAM,CAAN,CAIJ,EAJgBA,CAAA,CAAM,CAAN,CAIhB,GAJ0BwE,CAI1B,CAJgC,SAIhC,CAJ4CA,CAI5C,EAHA9F,CAGA,CAHIsB,CAAAS,MAGJ,CAFA6D,CAAA,CAAQI,CAAAC,OAAA,CAAW,CAAX,CAAcjG,CAAd,CAAR,CAEA,CADA6F,CAAA,CAAQC,CAAR,CAAaxE,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiBsE,CAAjB,CAAgC,EAAhC,CAAb,CACA,CAAAM,CAAA,CAAMA,CAAAtD,UAAA,CAAc1C,CAAd,CAAkBsB,CAAA,CAAM,CAAN,CAAArB,OAAlB,CAER2F,EAAA,CAAQI,CAAR,CACA,OAAOR,EAAA,CAAUrF,CAAAT,KAAA,CAAU,EAAV,CAAV,CAlBqB,CAL+C,CAAlC,CAA7C,CAzjBsC,CAArC,CAAA,CA0mBET,MA1mBF,CA0mBUA,MAAAC,QA1mBV;", +"sources":["angular-sanitize.js"], +"names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","obj","items","split","i","length","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","lowercase","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","stack.last","specialElements","RegExp","all","text","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","parts","spaceRe","exec","spaceBefore","spaceAfter","content","hiddenPre","innerHTML","textContent","innerText","encodeEntities","NON_ALPHANUMERIC_REGEXP","charCodeAt","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"] +} \ No newline at end of file diff --git a/setup/pub/magento/setup/add-database.js b/setup/pub/magento/setup/add-database.js index 841d8e81ccdf9..e255483f4afc6 100644 --- a/setup/pub/magento/setup/add-database.js +++ b/setup/pub/magento/setup/add-database.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/app.js b/setup/pub/magento/setup/app.js index ec890084324a8..c74f4a1551a96 100644 --- a/setup/pub/magento/setup/app.js +++ b/setup/pub/magento/setup/app.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/auth-dialog.js b/setup/pub/magento/setup/auth-dialog.js index b2b905120a3f8..9e2fcf0537d86 100644 --- a/setup/pub/magento/setup/auth-dialog.js +++ b/setup/pub/magento/setup/auth-dialog.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/complete-backup.js b/setup/pub/magento/setup/complete-backup.js index d1d404853eb05..19062731a7a4c 100644 --- a/setup/pub/magento/setup/complete-backup.js +++ b/setup/pub/magento/setup/complete-backup.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/create-admin-account.js b/setup/pub/magento/setup/create-admin-account.js index dd8357dd9a568..905406110684e 100644 --- a/setup/pub/magento/setup/create-admin-account.js +++ b/setup/pub/magento/setup/create-admin-account.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/create-backup.js b/setup/pub/magento/setup/create-backup.js index 2ff55e4087d00..b4a56d42b8278 100644 --- a/setup/pub/magento/setup/create-backup.js +++ b/setup/pub/magento/setup/create-backup.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/customize-your-store.js b/setup/pub/magento/setup/customize-your-store.js index bcec183f34a0c..9242f59b3a7dc 100644 --- a/setup/pub/magento/setup/customize-your-store.js +++ b/setup/pub/magento/setup/customize-your-store.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/data-option.js b/setup/pub/magento/setup/data-option.js index b90d42e2dc840..ca003ba3f9321 100644 --- a/setup/pub/magento/setup/data-option.js +++ b/setup/pub/magento/setup/data-option.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -25,4 +25,4 @@ angular.module('data-option', ['ngStorage']) $scope.$watch('component.dataOption', function(value) { $localStorage.dataOption = value; }); - }]); \ No newline at end of file + }]); diff --git a/setup/pub/magento/setup/extension-grid.js b/setup/pub/magento/setup/extension-grid.js index e5fc525dbd1bc..e657a0955b3d3 100644 --- a/setup/pub/magento/setup/extension-grid.js +++ b/setup/pub/magento/setup/extension-grid.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/home.js b/setup/pub/magento/setup/home.js index 3c72130be860c..7959eb3bcf5d7 100644 --- a/setup/pub/magento/setup/home.js +++ b/setup/pub/magento/setup/home.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/install-extension-grid.js b/setup/pub/magento/setup/install-extension-grid.js index 0ec3b66911f10..964c4f9dd3f21 100644 --- a/setup/pub/magento/setup/install-extension-grid.js +++ b/setup/pub/magento/setup/install-extension-grid.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/install.js b/setup/pub/magento/setup/install.js index e1c7c0a125336..47c82736bb622 100644 --- a/setup/pub/magento/setup/install.js +++ b/setup/pub/magento/setup/install.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/landing.js b/setup/pub/magento/setup/landing.js index 31ef19adb6b40..5c508a65cdbcc 100644 --- a/setup/pub/magento/setup/landing.js +++ b/setup/pub/magento/setup/landing.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/main.js b/setup/pub/magento/setup/main.js index 3f359df3232d7..e307319088c26 100644 --- a/setup/pub/magento/setup/main.js +++ b/setup/pub/magento/setup/main.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/marketplace-credentials.js b/setup/pub/magento/setup/marketplace-credentials.js index ec7bb6d254873..f65bee361e9b3 100644 --- a/setup/pub/magento/setup/marketplace-credentials.js +++ b/setup/pub/magento/setup/marketplace-credentials.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/module-grid.js b/setup/pub/magento/setup/module-grid.js index 21dec8795f135..8c5b0c48ba670 100644 --- a/setup/pub/magento/setup/module-grid.js +++ b/setup/pub/magento/setup/module-grid.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/readiness-check.js b/setup/pub/magento/setup/readiness-check.js index 1b89ea2be06a5..ae84cd10fdbef 100644 --- a/setup/pub/magento/setup/readiness-check.js +++ b/setup/pub/magento/setup/readiness-check.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/remove-dialog.js b/setup/pub/magento/setup/remove-dialog.js index 96a63ec5193b2..6ce02ba511128 100644 --- a/setup/pub/magento/setup/remove-dialog.js +++ b/setup/pub/magento/setup/remove-dialog.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/select-version.js b/setup/pub/magento/setup/select-version.js index 6a6ecdd9a99f1..dbfc315437ad5 100644 --- a/setup/pub/magento/setup/select-version.js +++ b/setup/pub/magento/setup/select-version.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/start-updater.js b/setup/pub/magento/setup/start-updater.js index 5b90c5333cdd1..e1ed53bab3f70 100644 --- a/setup/pub/magento/setup/start-updater.js +++ b/setup/pub/magento/setup/start-updater.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/success.js b/setup/pub/magento/setup/success.js index ed23aa8c37133..df06d5f89470c 100644 --- a/setup/pub/magento/setup/success.js +++ b/setup/pub/magento/setup/success.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/system-config.js b/setup/pub/magento/setup/system-config.js index b8f6f16725802..bb2a2d0612c19 100644 --- a/setup/pub/magento/setup/system-config.js +++ b/setup/pub/magento/setup/system-config.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/update-extension-grid.js b/setup/pub/magento/setup/update-extension-grid.js index 8f7149bf1645e..4450444972e5d 100644 --- a/setup/pub/magento/setup/update-extension-grid.js +++ b/setup/pub/magento/setup/update-extension-grid.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/updater-success.js b/setup/pub/magento/setup/updater-success.js index 36e58958ed691..0f44d1a04cee5 100644 --- a/setup/pub/magento/setup/updater-success.js +++ b/setup/pub/magento/setup/updater-success.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/pub/magento/setup/web-configuration.js b/setup/pub/magento/setup/web-configuration.js index 03a0fc7845dda..63dec9ead2aab 100644 --- a/setup/pub/magento/setup/web-configuration.js +++ b/setup/pub/magento/setup/web-configuration.js @@ -1,11 +1,11 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ 'use strict'; angular.module('web-configuration', ['ngStorage']) - .controller('webConfigurationController', ['$scope', '$state', '$localStorage', function ($scope, $state, $localStorage) { + .controller('webConfigurationController', ['$scope', '$state', '$localStorage', '$http', function ($scope, $state, $localStorage, $http) { $scope.config = { address: { base_url: '', @@ -119,4 +119,28 @@ angular.module('web-configuration', ['ngStorage']) $scope.webconfig.submitted = false; } }); + + // Validate URL + $scope.validateUrl = function () { + if (!$scope.webconfig.submitted) { + $http.post('index.php/url-check', $scope.config) + .success(function (data) { + $scope.validateUrl.result = data; + if ($scope.validateUrl.result.successUrl && $scope.validateUrl.result.successSecureUrl) { + $scope.nextState(); + } + if (!$scope.validateUrl.result.successUrl) { + $scope.webconfig.submitted = true; + $scope.webconfig.base_url.$setValidity('url', false); + } + if (!$scope.validateUrl.result.successSecureUrl) { + $scope.webconfig.submitted = true; + $scope.webconfig.https.$setValidity('url', false); + } + }) + .error(function (data) { + $scope.validateUrl.failed = data; + }); + } + }; }]); diff --git a/setup/pub/styles/setup.css b/setup/pub/styles/setup.css index 61893c04c3f16..980e8233a0950 100644 --- a/setup/pub/styles/setup.css +++ b/setup/pub/styles/setup.css @@ -1,6 +1,6 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -.abs-action-delete,.abs-icon,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.validation-symbol:after{color:#e22626;content:'*';font-weight:400;margin-left:3px}.abs-modal-overlay,.modals-overlay{background:rgba(0,0,0,.35);bottom:0;left:0;position:fixed;right:0;top:0}.abs-action-delete>span,.abs-visually-hidden,.action-multicheck-wrap .action-multicheck-toggle>span,.admin__actions-switch-checkbox,.admin__control-fields .admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label)>.admin__field-label,.admin__field-tooltip .admin__field-tooltip-action span,.customize-your-store .customize-your-store-default .legend,.extensions-information .list .extension-delete>span,.form-el-checkbox,.form-el-radio,.selectmenu .action-delete>span,.selectmenu .action-edit>span,.selectmenu .action-save>span,.selectmenu-toggle span,.tooltip .help a span,.tooltip .help span span,[class*=admin__control-grouped]>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.abs-visually-hidden-reset,.admin__field-group-columns>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label[class]{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.abs-clearfix:after,.abs-clearfix:before,.action-multicheck-wrap:after,.action-multicheck-wrap:before,.actions-split:after,.actions-split:before,.admin__control-table-pagination:after,.admin__control-table-pagination:before,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:before,.admin__data-grid-filters-footer:after,.admin__data-grid-filters-footer:before,.admin__data-grid-filters:after,.admin__data-grid-filters:before,.admin__data-grid-header-row:after,.admin__data-grid-header-row:before,.admin__field-complex:after,.admin__field-complex:before,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .magento-message .insert-title-inner:before,.modal-slide .main-col .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:before,.page-actions._fixed:after,.page-actions._fixed:before,.page-content:after,.page-content:before,.page-header-actions:after,.page-header-actions:before,.page-main-actions:not(._hidden):after,.page-main-actions:not(._hidden):before{content:'';display:table}.abs-clearfix:after,.action-multicheck-wrap:after,.actions-split:after,.admin__control-table-pagination:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-filters-footer:after,.admin__data-grid-filters:after,.admin__data-grid-header-row:after,.admin__field-complex:after,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:after,.page-actions._fixed:after,.page-content:after,.page-header-actions:after,.page-main-actions:not(._hidden):after{clear:both}.abs-list-reset-styles{margin:0;padding:0;list-style:none}.abs-draggable-handle,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle,.admin__control-table .draggable-handle,.data-grid .data-grid-draggable-row-cell .draggable-handle{cursor:-webkit-grab;cursor:move;font-size:0;margin-top:-4px;padding:0 1rem 0 0;vertical-align:middle;display:inline-block;text-decoration:none}.abs-draggable-handle:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:before,.admin__control-table .draggable-handle:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:before{-webkit-font-smoothing:antialiased;font-size:1.8rem;line-height:inherit;color:#9e9e9e;content:'\e617';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.abs-draggable-handle:hover:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:hover:before,.admin__control-table .draggable-handle:hover:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:hover:before{color:#858585}.abs-config-scope-label,.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]:before{bottom:-1.3rem;color:gray;content:attr(data-config-scope);font-size:1.1rem;font-weight:400;min-width:15rem;position:absolute;right:0;text-transform:lowercase}.abs-word-wrap,.admin__field:not(.admin__field-option)>.admin__field-label{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;box-sizing:border-box}*,:after,:before{box-sizing:inherit}:focus{box-shadow:none;outline:0}._keyfocus :focus{box-shadow:0 0 0 1px #008bdb}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}embed,img,object,video{max-width:100%}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/light/opensans-300.eot);src:url(../fonts/opensans/light/opensans-300.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/light/opensans-300.woff2) format('woff2'),url(../fonts/opensans/light/opensans-300.woff) format('woff'),url(../fonts/opensans/light/opensans-300.ttf) format('truetype'),url('../fonts/opensans/light/opensans-300.svg#Open Sans') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/regular/opensans-400.eot);src:url(../fonts/opensans/regular/opensans-400.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/regular/opensans-400.woff2) format('woff2'),url(../fonts/opensans/regular/opensans-400.woff) format('woff'),url(../fonts/opensans/regular/opensans-400.ttf) format('truetype'),url('../fonts/opensans/regular/opensans-400.svg#Open Sans') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/semibold/opensans-600.eot);src:url(../fonts/opensans/semibold/opensans-600.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/semibold/opensans-600.woff2) format('woff2'),url(../fonts/opensans/semibold/opensans-600.woff) format('woff'),url(../fonts/opensans/semibold/opensans-600.ttf) format('truetype'),url('../fonts/opensans/semibold/opensans-600.svg#Open Sans') format('svg');font-weight:600;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/bold/opensans-700.eot);src:url(../fonts/opensans/bold/opensans-700.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/bold/opensans-700.woff2) format('woff2'),url(../fonts/opensans/bold/opensans-700.woff) format('woff'),url(../fonts/opensans/bold/opensans-700.ttf) format('truetype'),url('../fonts/opensans/bold/opensans-700.svg#Open Sans') format('svg');font-weight:700;font-style:normal}html{font-size:62.5%}body{color:#333;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.36;font-size:1.4rem}h1{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2.8rem}h2{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2rem}h3{margin:0 0 2rem;color:#41362f;font-weight:600;line-height:1.2;font-size:1.7rem}h4,h5,h6{font-weight:600;margin-top:0}p{margin:0 0 1em}small{font-size:1.2rem}a{color:#008bdb;text-decoration:none}a:hover{color:#0fa7ff;text-decoration:underline}dl,ol,ul{padding-left:0}nav ol,nav ul{list-style:none;margin:0;padding:0}html{height:100%}body{background-color:#fff;min-height:100%;min-width:102.4rem}.page-wrapper{background-color:#fff;display:inline-block;margin-left:-4px;vertical-align:top;width:calc(100% - 8.8rem)}.page-content{padding-bottom:3rem;padding-left:3rem;padding-right:3rem}.notices-wrapper{margin:0 3rem}.notices-wrapper .messages{margin-bottom:0}.row{margin-left:0;margin-right:0}.row:after{clear:both;content:'';display:table}.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9,.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{min-height:1px;padding-left:0;padding-right:0;position:relative}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}.row-gutter{margin-left:-1.5rem;margin-right:-1.5rem}.row-gutter>[class*=col-]{padding-left:1.5rem;padding-right:1.5rem}.abs-clearer:after,.extension-manager-content:after,.extension-manager-title:after,.form-row:after,.header:after,.nav:after,body:after{clear:both;content:'';display:table}.ng-cloak{display:none!important}.hide.hide{display:none}.show.show{display:block}.text-center{text-align:center}.text-right{text-align:right}@font-face{font-family:Icons;src:url(../fonts/icons/icons.eot);src:url(../fonts/icons/icons.eot?#iefix) format('embedded-opentype'),url(../fonts/icons/icons.woff2) format('woff2'),url(../fonts/icons/icons.woff) format('woff'),url(../fonts/icons/icons.ttf) format('truetype'),url(../fonts/icons/icons.svg#Icons) format('svg');font-weight:400;font-style:normal}[class*=icon-]{display:inline-block;line-height:1}.icon-failed:before,.icon-success:before,[class*=icon-]:after{font-family:Icons}.icon-success{color:#79a22e}.icon-success:before{content:'\e62d'}.icon-failed{color:#e22626}.icon-failed:before{content:'\e632'}.icon-success-thick:after{content:'\e62d'}.icon-collapse:after{content:'\e615'}.icon-failed-thick:after{content:'\e632'}.icon-expand:after{content:'\e616'}.icon-warning:after{content:'\e623'}.icon-failed-round,.icon-success-round{border-radius:100%;color:#fff;font-size:2.5rem;height:1em;position:relative;text-align:center;width:1em}.icon-failed-round:after,.icon-success-round:after{bottom:0;font-size:.5em;left:0;position:absolute;right:0;top:.45em}.icon-success-round{background-color:#79a22e}.icon-success-round:after{content:'\e62d'}.icon-failed-round{background-color:#e22626}.icon-failed-round:after{content:'\e632'}dl,ol,ul{margin-top:0}.list{padding-left:0}.list>li{display:block;margin-bottom:.75em;position:relative}.list>li>.icon-failed,.list>li>.icon-success{font-size:1.6em;left:-.1em;position:absolute;top:0}.list>li>.icon-success{color:#79a22e}.list>li>.icon-failed{color:#e22626}.list-item-failed,.list-item-icon,.list-item-success,.list-item-warning{padding-left:3.5rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{left:-.1em;position:absolute}.list-item-success:before{color:#79a22e}.list-item-failed:before{color:#e22626}.list-item-warning:before{color:#ef672f}.list-definition{margin:0 0 3rem;padding:0}.list-definition>dt{clear:left;float:left}.list-definition>dd{margin-bottom:1em;margin-left:20rem}.btn-wrap{margin:0 auto}.btn-wrap .btn{width:100%}.btn{background:#e3e3e3;border:none;color:#514943;display:inline-block;font-size:1.6rem;font-weight:600;padding:.45em .9em;text-align:center}.btn:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.btn:active{background-color:#d6d6d6}.btn.disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.ie9 .btn.disabled,.ie9 .btn[disabled]{background-color:#f0f0f0;opacity:1;text-shadow:none}.btn-large{padding:.75em 1.25em}.btn-medium{font-size:1.4rem;padding:.5em 1.5em .6em}.btn-link{background-color:transparent;border:none;color:#008bdb;font-family:1.6rem;font-size:1.5rem}.btn-link:active,.btn-link:focus,.btn-link:hover{background-color:transparent;color:#0fa7ff}.btn-prime{background-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.btn-prime:focus,.btn-prime:hover{background-color:#f65405;background-repeat:repeat-x;background-image:linear-gradient(to right,#e04f00 0,#f65405 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e04f00', endColorstr='#f65405', GradientType=1);color:#fff}.btn-prime:active{background-color:#e04f00;background-repeat:repeat-x;background-image:linear-gradient(to right,#f65405 0,#e04f00 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f65405', endColorstr='#e04f00', GradientType=1);color:#fff}.ie9 .btn-prime.disabled,.ie9 .btn-prime[disabled]{background-color:#fd6e23}.ie9 .btn-prime.disabled:active,.ie9 .btn-prime.disabled:hover,.ie9 .btn-prime[disabled]:active,.ie9 .btn-prime[disabled]:hover{background-color:#fd6e23;-webkit-filter:none;filter:none}.btn-secondary{background-color:#514943;color:#fff}.btn-secondary:hover{background-color:#5f564f;color:#fff}.btn-secondary:active,.btn-secondary:focus{background-color:#574e48;color:#fff}.ie9 .btn-secondary.disabled,.ie9 .btn-secondary[disabled]{background-color:#514943}.ie9 .btn-secondary.disabled:active,.ie9 .btn-secondary[disabled]:active{background-color:#514943;-webkit-filter:none;filter:none}[class*=btn-wrap-triangle]{overflow:hidden;position:relative}[class*=btn-wrap-triangle] .btn:after{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.btn-wrap-triangle-right{display:inline-block;padding-right:1.74rem;position:relative}.btn-wrap-triangle-right .btn{text-indent:.92rem}.btn-wrap-triangle-right .btn:after{border-color:transparent transparent transparent #e3e3e3;border-width:1.84rem 0 1.84rem 1.84rem;left:100%;margin-left:-1.74rem}.btn-wrap-triangle-right .btn:focus:after,.btn-wrap-triangle-right .btn:hover:after{border-left-color:#dbdbdb}.btn-wrap-triangle-right .btn:active:after{border-left-color:#d6d6d6}.btn-wrap-triangle-right .btn:not(.disabled):active,.btn-wrap-triangle-right .btn:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn.disabled:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:after{border-color:transparent transparent transparent #f0f0f0}.ie9 .btn-wrap-triangle-right .btn.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn.disabled:focus:after,.ie9 .btn-wrap-triangle-right .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:focus:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:hover:after{border-left-color:#f0f0f0}.btn-wrap-triangle-right .btn-prime:after{border-color:transparent transparent transparent #eb5202}.btn-wrap-triangle-right .btn-prime:focus:after,.btn-wrap-triangle-right .btn-prime:hover:after{border-left-color:#f65405}.btn-wrap-triangle-right .btn-prime:active:after{border-left-color:#e04f00}.btn-wrap-triangle-right .btn-prime:not(.disabled):active,.btn-wrap-triangle-right .btn-prime:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:after{border-color:transparent transparent transparent #fd6e23}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:hover:after{border-left-color:#fd6e23}.btn-wrap-triangle-left{display:inline-block;padding-left:1.74rem}.btn-wrap-triangle-left .btn{text-indent:-.92rem}.btn-wrap-triangle-left .btn:after{border-color:transparent #e3e3e3 transparent transparent;border-width:1.84rem 1.84rem 1.84rem 0;margin-right:-1.74rem;right:100%}.btn-wrap-triangle-left .btn:focus:after,.btn-wrap-triangle-left .btn:hover:after{border-right-color:#dbdbdb}.btn-wrap-triangle-left .btn:active:after{border-right-color:#d6d6d6}.btn-wrap-triangle-left .btn:not(.disabled):active,.btn-wrap-triangle-left .btn:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn.disabled:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:after{border-color:transparent #f0f0f0 transparent transparent}.ie9 .btn-wrap-triangle-left .btn.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:hover:after{border-right-color:#f0f0f0}.btn-wrap-triangle-left .btn-prime:after{border-color:transparent #eb5202 transparent transparent}.btn-wrap-triangle-left .btn-prime:focus:after,.btn-wrap-triangle-left .btn-prime:hover:after{border-right-color:#e04f00}.btn-wrap-triangle-left .btn-prime:active:after{border-right-color:#f65405}.btn-wrap-triangle-left .btn-prime:not(.disabled):active,.btn-wrap-triangle-left .btn-prime:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:after{border-color:transparent #fd6e23 transparent transparent}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:hover:after{border-right-color:#fd6e23}.btn-expand{background-color:transparent;border:none;color:#303030;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700;padding:0;position:relative}.btn-expand.expanded:after{border-color:transparent transparent #303030;border-width:0 .285em .36em}.btn-expand.expanded:hover:after{border-color:transparent transparent #3d3d3d}.btn-expand:hover{background-color:transparent;border:none;color:#3d3d3d}.btn-expand:hover:after{border-color:#3d3d3d transparent transparent}.btn-expand:after{border-color:#303030 transparent transparent;border-style:solid;border-width:.36em .285em 0;content:'';height:0;left:100%;margin-left:.5em;margin-top:-.18em;position:absolute;top:50%;width:0}[class*=col-] .form-el-input,[class*=col-] .form-el-select{width:100%}.form-fieldset{border:none;margin:0 0 1em;padding:0}.form-row{margin-bottom:2.2rem}.form-row .form-row{margin-bottom:.4rem}.form-row .form-label{display:block;font-weight:600;padding:.6rem 2.1em 0 0;text-align:right}.form-row .form-label.required{position:relative}.form-row .form-label.required:after{color:#eb5202;content:'*';font-size:1.15em;position:absolute;right:.7em;top:.5em}.form-row .form-el-checkbox+.form-label:before,.form-row .form-el-radio+.form-label:before{top:.7rem}.form-row .form-el-checkbox+.form-label:after,.form-row .form-el-radio+.form-label:after{top:1.1rem}.form-row.form-row-text{padding-top:.6rem}.form-row.form-row-text .action-sign-out{font-size:1.2rem;margin-left:1rem}.form-note{font-size:1.2rem;font-weight:600;margin-top:1rem}.form-el-dummy{display:none}.fieldset{border:0;margin:0;min-width:0;padding:0}input:not([disabled]):focus,textarea:not([disabled]):focus{box-shadow:none}.form-el-input{border:1px solid #adadad;color:#303030;padding:.35em .55em .5em}.form-el-input:hover{border-color:#949494}.form-el-input:focus{border-color:#008bdb}.form-el-input:required{box-shadow:none}.form-label{margin-bottom:.5em}[class*=form-label][for]{cursor:pointer}.form-el-insider-wrap{display:table;width:100%}.form-el-insider-input{display:table-cell;width:100%}.form-el-insider{border-radius:2px;display:table-cell;padding:.43em .55em .5em 0;vertical-align:top}.form-legend,.form-legend-expand,.form-legend-light{display:block;margin:0}.form-legend,.form-legend-expand{font-size:1.25em;font-weight:600;margin-bottom:2.5em;padding-top:1.5em}.form-legend{border-top:1px solid #ccc;width:100%}.form-legend-light{font-size:1em;margin-bottom:1.5em}.form-legend-expand{cursor:pointer;transition:opacity .2s linear}.form-legend-expand:hover{opacity:.85}.form-legend-expand.expanded:after{content:'\e615'}.form-legend-expand:after{content:'\e616';font-family:Icons;font-size:1.15em;font-weight:400;margin-left:.5em;vertical-align:sub}.form-el-checkbox.disabled+.form-label,.form-el-checkbox.disabled+.form-label:before,.form-el-checkbox[disabled]+.form-label,.form-el-checkbox[disabled]+.form-label:before,.form-el-radio.disabled+.form-label,.form-el-radio.disabled+.form-label:before,.form-el-radio[disabled]+.form-label,.form-el-radio[disabled]+.form-label:before{cursor:default;opacity:.5;pointer-events:none}.form-el-checkbox:not(.disabled)+.form-label:hover:before,.form-el-checkbox:not([disabled])+.form-label:hover:before,.form-el-radio:not(.disabled)+.form-label:hover:before,.form-el-radio:not([disabled])+.form-label:hover:before{border-color:#514943}.form-el-checkbox+.form-label,.form-el-radio+.form-label{font-weight:400;padding-left:2em;padding-right:0;position:relative;text-align:left;transition:border-color .1s linear}.form-el-checkbox+.form-label:before,.form-el-radio+.form-label:before{border:1px solid;content:'';left:0;position:absolute;top:.1rem;transition:border-color .1s linear}.form-el-checkbox+.form-label:before{background-color:#fff;border-color:#adadad;border-radius:2px;font-size:1.2rem;height:1.6rem;line-height:1.2;width:1.6rem}.form-el-checkbox:checked+.form-label::before{content:'\e62d';font-family:Icons}.form-el-radio+.form-label:before{background-color:#fff;border:1px solid #adadad;border-radius:100%;height:1.8rem;width:1.8rem}.form-el-radio+.form-label:after{background:0 0;border:.5rem solid transparent;border-radius:100%;content:'';height:0;left:.4rem;position:absolute;top:.5rem;transition:background .3s linear;width:0}.form-el-radio:checked+.form-label{cursor:default}.form-el-radio:checked+.form-label:after{border-color:#514943}.form-select-label{border:1px solid #adadad;color:#303030;cursor:pointer;display:block;overflow:hidden;position:relative;z-index:0}.form-select-label:hover,.form-select-label:hover:after{border-color:#949494}.form-select-label:active,.form-select-label:active:after,.form-select-label:focus,.form-select-label:focus:after{border-color:#008bdb}.form-select-label:after{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:2.36em;z-index:-2}.ie9 .form-select-label:after{display:none}.form-select-label:before{border-color:#303030 transparent transparent;border-style:solid;border-width:5px 4px 0;content:'';height:0;margin-right:-4px;margin-top:-2.5px;position:absolute;right:1.18em;top:50%;width:0;z-index:-1}.ie9 .form-select-label:before{display:none}.form-select-label .form-el-select{background:0 0;border:none;border-radius:0;content:'';display:block;margin:0;padding:.35em calc(2.36em + 10%) .5em .55em;width:110%}.ie9 .form-select-label .form-el-select{padding-right:.55em;width:100%}.form-select-label .form-el-select::-ms-expand{display:none}.form-el-select{background:#fff;border:1px solid #adadad;border-radius:2px;color:#303030;display:block;padding:.35em .55em}.multiselect-custom{border:1px solid #adadad;height:45.2rem;margin:0 0 1.5rem;overflow:auto;position:relative}.multiselect-custom ul{margin:0;padding:0;list-style:none;min-width:29rem}.multiselect-custom .item{padding:1rem 1.4rem}.multiselect-custom .selected{background-color:#e0f6fe}.multiselect-custom .form-label{margin-bottom:0}[class*=form-el-].invalid{border-color:#e22626}[class*=form-el-].invalid+.error-container{display:block}.error-container{background-color:#fffbbb;border:1px solid #ee7d7d;color:#514943;display:none;font-size:1.19rem;margin-top:.2rem;padding:.8rem 1rem .9rem}.check-result-message{margin-left:.5em;min-height:3.68rem;-ms-align-items:center;-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.check-result-text{margin-left:.5em}body:not([class]){min-width:0}.container{display:block;margin:0 auto 4rem;max-width:100rem;padding:0}.abs-action-delete,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.text-stretch{margin-bottom:1.5em}.page-title-jumbo{font-size:4rem;font-weight:300;letter-spacing:-.05em;margin-bottom:2.9rem}.page-title-jumbo-success:before{color:#79a22e;content:'\e62d';font-size:3.9rem;margin-left:-.3rem;margin-right:2.4rem}.list{margin-bottom:3rem}.list-dot .list-item{display:list-item;list-style-position:inside;margin-bottom:1.2rem}.list-title{color:#333;font-size:1.4rem;font-weight:700;letter-spacing:.025em;margin-bottom:1.2rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{font-family:Icons;font-size:1.6rem;top:0}.list-item-success:before{content:'\e62d';font-size:1.6rem}.list-item-failed:before{content:'\e632';font-size:1.4rem;left:.1rem;top:.2rem}.list-item-warning:before{content:'\e623';font-size:1.3rem;left:.2rem}.form-wrap{margin-bottom:3.6rem;padding-top:2.1rem}.form-el-label-horizontal{display:inline-block;font-size:1.3rem;font-weight:600;letter-spacing:.025em;margin-bottom:.4rem;margin-left:.4rem}.app-updater{min-width:768px}body._has-modal{height:100%;overflow:hidden;width:100%}.modals-overlay{z-index:899}.modal-popup,.modal-slide{bottom:0;min-width:0;position:fixed;right:0;top:0;visibility:hidden}.modal-popup._show,.modal-slide._show{visibility:visible}.modal-popup._show .modal-inner-wrap,.modal-slide._show .modal-inner-wrap{-ms-transform:translate(0,0);transform:translate(0,0)}.modal-popup .modal-inner-wrap,.modal-slide .modal-inner-wrap{background-color:#fff;box-shadow:0 0 12px 2px rgba(0,0,0,.35);opacity:1;pointer-events:auto}.modal-slide{left:14.8rem;z-index:900}.modal-slide._show .modal-inner-wrap{-ms-transform:translateX(0);transform:translateX(0)}.modal-slide .modal-inner-wrap{height:100%;overflow-y:auto;position:static;-ms-transform:translateX(100%);transform:translateX(100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;width:auto}.modal-slide._inner-scroll .modal-inner-wrap{overflow-y:visible;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.modal-slide._inner-scroll .modal-footer,.modal-slide._inner-scroll .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-slide._inner-scroll .modal-content{overflow-y:auto}.modal-slide._inner-scroll .modal-footer{margin-top:auto}.modal-slide .modal-content,.modal-slide .modal-footer,.modal-slide .modal-header{padding:0 2.6rem 2.6rem}.modal-slide .modal-header{padding-bottom:2.1rem;padding-top:2.1rem}.modal-popup{z-index:900;left:0;overflow-y:auto}.modal-popup._show .modal-inner-wrap{-ms-transform:translateY(0);transform:translateY(0)}.modal-popup .modal-inner-wrap{margin:5rem auto;width:75%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;box-sizing:border-box;height:auto;left:0;position:absolute;right:0;-ms-transform:translateY(-200%);transform:translateY(-200%);transition-duration:.2s;transition-property:transform,visibility;transition-timing-function:ease}.modal-popup._inner-scroll{overflow-y:visible}.ie10 .modal-popup._inner-scroll,.ie9 .modal-popup._inner-scroll{overflow-y:auto}.modal-popup._inner-scroll .modal-inner-wrap{max-height:90%}.ie10 .modal-popup._inner-scroll .modal-inner-wrap,.ie9 .modal-popup._inner-scroll .modal-inner-wrap{max-height:none}.modal-popup._inner-scroll .modal-content{overflow-y:auto}.modal-popup .modal-content,.modal-popup .modal-footer,.modal-popup .modal-header{padding-left:3rem;padding-right:3rem}.modal-popup .modal-footer,.modal-popup .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-popup .modal-header{padding-bottom:1.2rem;padding-top:3rem}.modal-popup .modal-footer{margin-top:auto;padding-bottom:3rem}.modal-popup .modal-footer-actions{text-align:right}.admin__action-dropdown-wrap{display:inline-block;position:relative}.admin__action-dropdown-wrap .admin__action-dropdown-text:after{left:-6px;right:0}.admin__action-dropdown-wrap .admin__action-dropdown-menu{left:auto;right:0}.admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__action-dropdown-wrap.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin__action-dropdown-wrap._active .admin__action-dropdown-text:after,.admin__action-dropdown-wrap.active .admin__action-dropdown-text:after{background-color:#fff;content:'';height:6px;position:absolute;top:100%}.admin__action-dropdown-wrap._active .admin__action-dropdown-menu,.admin__action-dropdown-wrap.active .admin__action-dropdown-menu{display:block}.admin__action-dropdown-wrap._disabled .admin__action-dropdown{cursor:default}.admin__action-dropdown-wrap._disabled:hover .admin__action-dropdown{color:#333}.admin__action-dropdown{background-color:#fff;border:1px solid transparent;border-bottom:none;border-radius:0;box-shadow:none;color:#333;display:inline-block;font-size:1.3rem;font-weight:400;letter-spacing:-.025em;padding:.7rem 3.3rem .8rem 1.5rem;position:relative;vertical-align:baseline;z-index:2}.admin__action-dropdown._active:after,.admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .admin__action-dropdown:after,.active .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin__action-dropdown:focus,.admin__action-dropdown:hover{background-color:#fff;color:#000;text-decoration:none}.admin__action-dropdown:after{right:1.5rem}.admin__action-dropdown:before{margin-right:1rem}.admin__action-dropdown-menu{background-color:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;line-height:1.36;margin-top:-1px;min-width:120%;padding:.5rem 1rem;position:absolute;top:100%;transition:all .15s ease;z-index:1}.admin__action-dropdown-menu>li{display:block}.admin__action-dropdown-menu>li>a{color:#333;display:block;text-decoration:none;padding:.6rem .5rem}.selectmenu{display:inline-block;position:relative;text-align:left;z-index:1}.selectmenu._active{border-color:#007bdb;z-index:500}.selectmenu .action-delete,.selectmenu .action-edit,.selectmenu .action-save{background-color:transparent;border-color:transparent;box-shadow:none;padding:0 1rem}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover,.selectmenu .action-save:hover{background-color:transparent;border-color:transparent;box-shadow:none}.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before{content:'\e630'}.selectmenu .action-delete,.selectmenu .action-edit{border:0 solid #fff;border-left-width:1px;bottom:0;position:absolute;right:0;top:0;z-index:1}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover{border:0 solid #fff;border-left-width:1px}.selectmenu .action-save:before{content:'\e625'}.selectmenu .action-edit:before{content:'\e631'}.selectmenu-value{display:inline-block}.selectmenu-value input[type=text]{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:0;display:inline;margin:0;width:6rem}body._keyfocus .selectmenu-value input[type=text]:focus{box-shadow:none}.selectmenu-toggle{padding-right:3rem;background:0 0;border-width:0;bottom:0;float:right;position:absolute;right:0;top:0;width:0}.selectmenu-toggle._active:after,.selectmenu-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.1rem;top:50%;transition:all .2s linear;width:0}._active .selectmenu-toggle:after,.active .selectmenu-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:hover:after{border-color:#000 transparent transparent}.selectmenu-toggle:active,.selectmenu-toggle:focus,.selectmenu-toggle:hover{background:0 0}.selectmenu._active .selectmenu-toggle:before{border-color:#007bdb}body._keyfocus .selectmenu-toggle:focus{box-shadow:none}.selectmenu-toggle:before{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';display:block;position:absolute;right:0;top:0;width:3.2rem}.selectmenu-items{background:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;float:left;left:-1px;margin-top:3px;max-width:20rem;min-width:calc(100% + 2px);position:absolute;top:100%}.selectmenu-items._active{display:block}.selectmenu-items ul{float:left;list-style-type:none;margin:0;min-width:100%;padding:0}.selectmenu-items li{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row;transition:background .2s linear}.selectmenu-items li:hover{background:#e3e3e3}.selectmenu-items li:last-child .selectmenu-item-action,.selectmenu-items li:last-child .selectmenu-item-action:visited{color:#008bdb;text-decoration:none}.selectmenu-items li:last-child .selectmenu-item-action:hover{color:#0fa7ff;text-decoration:underline}.selectmenu-items li:last-child .selectmenu-item-action:active{color:#ff5501;text-decoration:underline}.selectmenu-item{position:relative;width:100%;z-index:1}li._edit>.selectmenu-item{display:none}.selectmenu-item-edit{display:none;padding:.3rem 4rem .3rem .4rem;position:relative;white-space:nowrap;z-index:1}li:last-child .selectmenu-item-edit{padding-right:.4rem}.selectmenu-item-edit .admin__control-text{margin:0;width:5.4rem}li._edit .selectmenu-item-edit{display:block}.selectmenu-item-action{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background:0 0;border:0;color:#333;display:block;font-size:1.4rem;font-weight:400;min-width:100%;padding:1rem 6rem 1rem 1.5rem;text-align:left;transition:background .2s linear;width:5rem}.selectmenu-item-action:focus,.selectmenu-item-action:hover{background:#e3e3e3}.abs-actions-split-xl .action-default,.page-actions .actions-split .action-default{margin-right:4rem}.abs-actions-split-xl .action-toggle,.page-actions .actions-split .action-toggle{padding-right:4rem}.abs-actions-split-xl .action-toggle:after,.page-actions .actions-split .action-toggle:after{border-width:.9rem .6rem 0;margin-top:-.3rem;right:1.4rem}.actions-split{position:relative;z-index:400}.actions-split._active,.actions-split.active,.actions-split:hover{box-shadow:0 0 0 1px #007bdb}.actions-split._active .action-toggle.action-primary,.actions-split._active .action-toggle.primary,.actions-split.active .action-toggle.action-primary,.actions-split.active .action-toggle.primary{background-color:#ba4000;border-color:#ba4000}.actions-split._active .dropdown-menu,.actions-split.active .dropdown-menu{opacity:1;visibility:visible;display:block}.actions-split .action-default,.actions-split .action-toggle{float:left;margin:0}.actions-split .action-default._active,.actions-split .action-default.active,.actions-split .action-default:hover,.actions-split .action-toggle._active,.actions-split .action-toggle.active,.actions-split .action-toggle:hover{box-shadow:none}.actions-split .action-default{margin-right:3.2rem;min-width:9.3rem}.actions-split .action-toggle{padding-right:3.2rem;border-left-color:rgba(0,0,0,.2);bottom:0;padding-left:0;position:absolute;right:0;top:0}.actions-split .action-toggle._active:after,.actions-split .action-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .actions-split .action-toggle:after,.active .actions-split .action-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:hover:after{border-color:#000 transparent transparent}.actions-split .action-toggle.action-primary:after,.actions-split .action-toggle.action-secondary:after,.actions-split .action-toggle.primary:after,.actions-split .action-toggle.secondary:after{border-color:#fff transparent transparent}.actions-split .action-toggle>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-select-wrap{display:inline-block;position:relative}.action-select-wrap .action-select{padding-right:3.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#fff;font-weight:400;text-align:left}.action-select-wrap .action-select._active:after,.action-select-wrap .action-select.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .action-select-wrap .action-select:after,.active .action-select-wrap .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:hover:after{border-color:#000 transparent transparent}.action-select-wrap .action-select:hover,.action-select-wrap .action-select:hover:before{border-color:#878787}.action-select-wrap .action-select:before{background-color:#e3e3e3;border:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:3.2rem}.action-select-wrap .action-select._active{border-color:#007bdb}.action-select-wrap .action-select._active:before{border-color:#007bdb #007bdb #007bdb #adadad}.action-select-wrap .action-select[disabled]{color:#333}.action-select-wrap .action-select[disabled]:after{border-color:#333 transparent transparent}.action-select-wrap._active{z-index:500}.action-select-wrap._active .action-select,.action-select-wrap._active .action-select:before{border-color:#007bdb}.action-select-wrap._active .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .abs-action-menu .action-submenu,.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu,.action-select-wrap .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:45rem;overflow-y:auto}.action-select-wrap .abs-action-menu .action-submenu ._disabled:hover,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .action-menu ._disabled:hover,.action-select-wrap .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled:hover{background:#fff}.action-select-wrap .abs-action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .action-menu ._disabled .action-menu-item,.action-select-wrap .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled .action-menu-item{cursor:default;opacity:.5}.action-select-wrap .action-menu-items{left:0;position:absolute;right:0;top:100%}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu{min-width:100%;position:static}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{position:absolute}.action-multicheck-wrap{display:inline-block;height:1.6rem;padding-top:1px;position:relative;width:3.1rem;z-index:200}.action-multicheck-wrap:hover .action-multicheck-toggle,.action-multicheck-wrap:hover .admin__control-checkbox+label:before{border-color:#878787}.action-multicheck-wrap._active .action-multicheck-toggle,.action-multicheck-wrap._active .admin__control-checkbox+label:before{border-color:#007bdb}.action-multicheck-wrap._active .abs-action-menu .action-submenu,.action-multicheck-wrap._active .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .action-menu,.action-multicheck-wrap._active .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu .action-submenu{opacity:1;visibility:visible;display:block}.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{background-color:#fff}.action-multicheck-wrap._disabled .action-multicheck-toggle,.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{border-color:#adadad;opacity:1}.action-multicheck-wrap .action-multicheck-toggle,.action-multicheck-wrap .admin__control-checkbox,.action-multicheck-wrap .admin__control-checkbox+label{float:left}.action-multicheck-wrap .action-multicheck-toggle{border-radius:0 1px 1px 0;height:1.6rem;margin-left:-1px;padding:0;position:relative;transition:border-color .1s linear;width:1.6rem}.action-multicheck-wrap .action-multicheck-toggle._active:after,.action-multicheck-wrap .action-multicheck-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .action-multicheck-wrap .action-multicheck-toggle:after,.active .action-multicheck-wrap .action-multicheck-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:hover:after{border-color:#000 transparent transparent}.action-multicheck-wrap .action-multicheck-toggle:focus{border-color:#007bdb}.action-multicheck-wrap .action-multicheck-toggle:after{right:.3rem}.action-multicheck-wrap .abs-action-menu .action-submenu,.action-multicheck-wrap .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap .action-menu,.action-multicheck-wrap .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:-1.1rem;margin-top:1px;right:auto;text-align:left}.action-multicheck-wrap .action-menu-item{white-space:nowrap}.admin__action-multiselect-wrap{display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.admin__action-multiselect-wrap.action-select-wrap:focus{box-shadow:none}.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .action-menu,.admin__action-multiselect-wrap.action-select-wrap .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:none;overflow-y:inherit}.admin__action-multiselect-wrap .action-menu-item{transition:background-color .1s linear}.admin__action-multiselect-wrap .action-menu-item._selected{background-color:#e0f6fe}.admin__action-multiselect-wrap .action-menu-item._hover{background-color:#e3e3e3}.admin__action-multiselect-wrap .action-menu-item._unclickable{cursor:default}.admin__action-multiselect-wrap .admin__action-multiselect{border:1px solid #adadad;cursor:pointer;display:block;min-height:3.2rem;padding-right:3.6rem;white-space:normal}.admin__action-multiselect-wrap .admin__action-multiselect:after{bottom:1.25rem;top:auto}.admin__action-multiselect-wrap .admin__action-multiselect:before{height:3.3rem;top:auto}.admin__control-table-wrapper .admin__action-multiselect-wrap{position:static}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect{position:relative}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect:before{right:-1px;top:-1px}.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:34rem;right:auto;top:auto;z-index:1}.admin__action-multiselect-wrap .admin__action-multiselect-item-path{color:#a79d95;font-size:1.2rem;font-weight:400;padding-left:1rem}.admin__action-multiselect-actions-wrap{border-top:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;text-align:center}.admin__action-multiselect-actions-wrap .action-default{font-size:1.3rem;min-width:13rem}.admin__action-multiselect-text{padding:.6rem 1rem}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{text-align:left}.admin__action-multiselect-label{cursor:pointer;position:relative;z-index:1}.admin__action-multiselect-label:before{margin-right:.5rem}._unclickable .admin__action-multiselect-label{cursor:default;font-weight:700}.admin__action-multiselect-search-wrap{border-bottom:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;position:relative}.admin__action-multiselect-search{padding-right:3rem;width:100%}.admin__action-multiselect-search-label{display:block;font-size:1.5rem;height:1em;overflow:hidden;position:absolute;right:2.2rem;top:1.7rem;width:1em}.admin__action-multiselect-search-label:before{content:'\e60c'}.admin__action-multiselect-search-count{color:#a79d95;margin-top:1rem}.admin__action-multiselect-menu-inner{margin-bottom:0;max-height:46rem;overflow-y:auto}.admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{list-style:none;max-height:none;overflow:hidden;padding-left:2.2rem}.admin__action-multiselect-menu-inner ._hidden{display:none}.admin__action-multiselect-crumb{background-color:#f5f5f5;border:1px solid #a79d95;border-radius:1px;display:inline-block;font-size:1.2rem;margin:.3rem -4px .3rem .3rem;padding:.3rem 2.4rem .4rem 1rem;position:relative;transition:border-color .1s linear}.admin__action-multiselect-crumb:hover{border-color:#908379}.admin__action-multiselect-crumb .action-close{bottom:0;font-size:.5em;position:absolute;right:0;top:0;width:2rem}.admin__action-multiselect-crumb .action-close:hover{color:#000}.admin__action-multiselect-crumb .action-close:active,.admin__action-multiselect-crumb .action-close:focus{background-color:transparent}.admin__action-multiselect-crumb .action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__action-multiselect-tree .abs-action-menu .action-submenu,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .action-menu,.admin__action-multiselect-tree .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu{min-width:34.7rem}.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item{margin-top:.1rem}.admin__action-multiselect-tree .action-menu-item{margin-left:4.2rem;position:relative}.admin__action-multiselect-tree .action-menu-item._expended:before{border-left:1px dashed #a79d95;bottom:0;content:'';left:-1rem;position:absolute;top:1rem;width:1px}.admin__action-multiselect-tree .action-menu-item._expended .admin__action-multiselect-dropdown:before{content:'\e615'}.admin__action-multiselect-tree .action-menu-item._with-checkbox .admin__action-multiselect-label{padding-left:2.6rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{padding-left:3.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner:before{left:4.3rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:last-child:before{height:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after,.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{content:'';left:0;position:absolute}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after{border-top:1px dashed #a79d95;height:1px;top:2.1rem;width:5.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{border-left:1px dashed #a79d95;height:100%;top:0;width:1px}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._parent:after{width:4.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root{margin-left:-1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:after{left:3.2rem;width:2.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:before{left:3.2rem;top:1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root._parent:after{display:none}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:first-child:before{top:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:last-child:before{height:1rem}.admin__action-multiselect-tree .admin__action-multiselect-label{line-height:2.2rem;vertical-align:middle;word-break:break-all}.admin__action-multiselect-tree .admin__action-multiselect-label:before{left:0;position:absolute;top:.4rem}.admin__action-multiselect-dropdown{border-radius:50%;height:2.2rem;left:-2.2rem;position:absolute;top:1rem;width:2.2rem;z-index:1}.admin__action-multiselect-dropdown:before{background:#fff;color:#a79d95;content:'\e616';font-size:2.2rem}.admin__actions-switch{display:inline-block;position:relative;vertical-align:middle}.admin__field-control .admin__actions-switch{line-height:3.2rem}.admin__actions-switch+.admin__field-service{min-width:34rem}._disabled .admin__actions-switch-checkbox+.admin__actions-switch-label,.admin__actions-switch-checkbox.disabled+.admin__actions-switch-label{cursor:not-allowed;opacity:.5;pointer-events:none}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:before{left:15px}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:after{background:#79a22e}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label .admin__actions-switch-text:before{content:attr(data-text-on)}.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:after,.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:before{border-color:#007bdb}._error .admin__actions-switch-checkbox+.admin__actions-switch-label:after,._error .admin__actions-switch-checkbox+.admin__actions-switch-label:before{border-color:#e22626}.admin__actions-switch-label{cursor:pointer;display:inline-block;height:22px;line-height:22px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.admin__actions-switch-label:after,.admin__actions-switch-label:before{left:0;position:absolute;right:auto;top:0}.admin__actions-switch-label:before{background:#fff;border:1px solid #aaa6a0;border-radius:100%;content:'';display:block;height:22px;transition:left .2s ease-in 0s;width:22px;z-index:1}.admin__actions-switch-label:after{background:#e3e3e3;border:1px solid #aaa6a0;border-radius:12px;content:'';display:block;height:22px;transition:background .2s ease-in 0s;vertical-align:middle;width:37px;z-index:0}.admin__actions-switch-text:before{content:attr(data-text-off);padding-left:47px;white-space:nowrap}.abs-action-delete,.abs-action-reset,.action-close,.admin__field-fallback-reset,.extensions-information .list .extension-delete,.notifications-close,.search-global-field._active .search-global-action{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0}.abs-action-delete:hover,.abs-action-reset:hover,.action-close:hover,.admin__field-fallback-reset:hover,.extensions-information .list .extension-delete:hover,.notifications-close:hover,.search-global-field._active .search-global-action:hover{background-color:transparent;border:none;box-shadow:none}.abs-action-default,.abs-action-pattern,.abs-action-primary,.abs-action-quaternary,.abs-action-secondary,.abs-action-tertiary,.action-default,.action-primary,.action-quaternary,.action-secondary,.action-tertiary,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions>button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary,button,button.primary,button.secondary,button.tertiary{border:1px solid;border-radius:0;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:1.36;padding:.6rem 1em;text-align:center;vertical-align:baseline}.abs-action-default.disabled,.abs-action-default[disabled],.abs-action-pattern.disabled,.abs-action-pattern[disabled],.abs-action-primary.disabled,.abs-action-primary[disabled],.abs-action-quaternary.disabled,.abs-action-quaternary[disabled],.abs-action-secondary.disabled,.abs-action-secondary[disabled],.abs-action-tertiary.disabled,.abs-action-tertiary[disabled],.action-default.disabled,.action-default[disabled],.action-primary.disabled,.action-primary[disabled],.action-quaternary.disabled,.action-quaternary[disabled],.action-secondary.disabled,.action-secondary[disabled],.action-tertiary.disabled,.action-tertiary[disabled],.modal-popup .modal-footer .action-primary.disabled,.modal-popup .modal-footer .action-primary[disabled],.modal-popup .modal-footer .action-secondary.disabled,.modal-popup .modal-footer .action-secondary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.action-secondary.disabled,.page-actions .page-actions-buttons>button.action-secondary[disabled],.page-actions .page-actions-buttons>button.disabled,.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions .page-actions-buttons>button[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.action-secondary.disabled,.page-actions>button.action-secondary[disabled],.page-actions>button.disabled,.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],.page-actions>button[disabled],button.disabled,button.primary.disabled,button.primary[disabled],button.secondary.disabled,button.secondary[disabled],button.tertiary.disabled,button.tertiary[disabled],button[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-l,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary{font-size:1.6rem;letter-spacing:.025em;padding-bottom:.6875em;padding-top:.6875em}.abs-action-delete,.extensions-information .list .extension-delete{display:inline-block;font-size:1.6rem;margin-left:1.2rem;padding-top:.7rem;text-decoration:none;vertical-align:middle}.abs-action-delete:after,.extensions-information .list .extension-delete:after{color:#666;content:'\e630'}.abs-action-delete:hover:after,.extensions-information .list .extension-delete:hover:after{color:#35302c}.abs-action-button-as-link,.action-advanced,.data-grid .action-delete{line-height:1.36;padding:0;color:#008bdb;text-decoration:none;background:0 0;border:0;display:inline;font-weight:400;border-radius:0}.abs-action-button-as-link:visited,.action-advanced:visited,.data-grid .action-delete:visited{color:#008bdb;text-decoration:none}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{text-decoration:underline}.abs-action-button-as-link:active,.action-advanced:active,.data-grid .action-delete:active{color:#ff5501;text-decoration:underline}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{color:#0fa7ff}.abs-action-button-as-link:active,.abs-action-button-as-link:focus,.abs-action-button-as-link:hover,.action-advanced:active,.action-advanced:focus,.action-advanced:hover,.data-grid .action-delete:active,.data-grid .action-delete:focus,.data-grid .action-delete:hover{background:0 0;border:0}.abs-action-button-as-link.disabled,.abs-action-button-as-link[disabled],.action-advanced.disabled,.action-advanced[disabled],.data-grid .action-delete.disabled,.data-grid .action-delete[disabled],fieldset[disabled] .abs-action-button-as-link,fieldset[disabled] .action-advanced,fieldset[disabled] .data-grid .action-delete{color:#008bdb;opacity:.5;cursor:default;pointer-events:none;text-decoration:underline}.abs-action-button-as-link:active,.abs-action-button-as-link:not(:focus),.action-advanced:active,.action-advanced:not(:focus),.data-grid .action-delete:active,.data-grid .action-delete:not(:focus){box-shadow:none}.abs-action-button-as-link:focus,.action-advanced:focus,.data-grid .action-delete:focus{color:#0fa7ff}.abs-action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.abs-action-default:active,.abs-action-default:focus,.abs-action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.abs-action-primary,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary,button.primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.abs-action-primary:active,.abs-action-primary:focus,.abs-action-primary:hover,.page-actions .page-actions-buttons>button.action-primary:active,.page-actions .page-actions-buttons>button.action-primary:focus,.page-actions .page-actions-buttons>button.action-primary:hover,.page-actions .page-actions-buttons>button.primary:active,.page-actions .page-actions-buttons>button.primary:focus,.page-actions .page-actions-buttons>button.primary:hover,.page-actions>button.action-primary:active,.page-actions>button.action-primary:focus,.page-actions>button.action-primary:hover,.page-actions>button.primary:active,.page-actions>button.primary:focus,.page-actions>button.primary:hover,button.primary:active,button.primary:focus,button.primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-primary.disabled,.abs-action-primary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],button.primary.disabled,button.primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-secondary,.modal-popup .modal-footer .action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions>button.action-secondary,button.secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.abs-action-secondary:active,.abs-action-secondary:focus,.abs-action-secondary:hover,.modal-popup .modal-footer .action-primary:active,.modal-popup .modal-footer .action-primary:focus,.modal-popup .modal-footer .action-primary:hover,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions .page-actions-buttons>button.action-secondary:focus,.page-actions .page-actions-buttons>button.action-secondary:hover,.page-actions>button.action-secondary:active,.page-actions>button.action-secondary:focus,.page-actions>button.action-secondary:hover,button.secondary:active,button.secondary:focus,button.secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-secondary:active,.modal-popup .modal-footer .action-primary:active,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions>button.action-secondary:active,button.secondary:active{background-color:#35302c}.abs-action-tertiary,.modal-popup .modal-footer .action-secondary,button.tertiary{background-color:transparent;border-color:transparent;text-shadow:none;color:#008bdb}.abs-action-tertiary:active,.abs-action-tertiary:focus,.abs-action-tertiary:hover,.modal-popup .modal-footer .action-secondary:active,.modal-popup .modal-footer .action-secondary:focus,.modal-popup .modal-footer .action-secondary:hover,button.tertiary:active,button.tertiary:focus,button.tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#0fa7ff;text-decoration:underline}.abs-action-quaternary,.page-actions .page-actions-buttons>button,.page-actions>button{background-color:transparent;border-color:transparent;text-shadow:none;color:#333}.abs-action-quaternary:active,.abs-action-quaternary:focus,.abs-action-quaternary:hover,.page-actions .page-actions-buttons>button:active,.page-actions .page-actions-buttons>button:focus,.page-actions .page-actions-buttons>button:hover,.page-actions>button:active,.page-actions>button:focus,.page-actions>button:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#1a1a1a}.abs-action-menu,.actions-split .abs-action-menu .action-submenu,.actions-split .abs-action-menu .action-submenu .action-submenu,.actions-split .action-menu,.actions-split .action-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.actions-split .dropdown-menu{text-align:left;background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu._active,.actions-split .abs-action-menu .action-submenu .action-submenu._active,.actions-split .abs-action-menu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .action-menu._active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .actions-split .dropdown-menu .action-submenu._active,.actions-split .dropdown-menu._active{display:block}.abs-action-menu>li,.actions-split .abs-action-menu .action-submenu .action-submenu>li,.actions-split .abs-action-menu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .action-menu>li,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .actions-split .dropdown-menu .action-submenu>li,.actions-split .dropdown-menu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu>li>a:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .abs-action-menu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .action-menu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu>li>a:hover{text-decoration:none}.abs-action-menu>li._visible,.abs-action-menu>li:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu .action-submenu>li:hover,.actions-split .abs-action-menu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .action-menu>li._visible,.actions-split .action-menu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu>li:hover,.actions-split .dropdown-menu>li._visible,.actions-split .dropdown-menu>li:hover{background-color:#e3e3e3}.abs-action-menu>li:active,.actions-split .abs-action-menu .action-submenu .action-submenu>li:active,.actions-split .abs-action-menu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .action-menu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu>li:active,.actions-split .dropdown-menu>li:active{background-color:#cacaca}.abs-action-menu>li._parent,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent,.actions-split .abs-action-menu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .action-menu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent,.actions-split .dropdown-menu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-menu-item,.abs-action-menu .item,.actions-split .abs-action-menu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .item,.actions-split .abs-action-menu .action-submenu .item,.actions-split .action-menu .action-menu-item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .item,.actions-split .action-menu .item,.actions-split .actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .actions-split .dropdown-menu .action-submenu .item,.actions-split .dropdown-menu .action-menu-item,.actions-split .dropdown-menu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu a.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .abs-action-menu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .action-menu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu a.action-menu-item{color:#333}.abs-action-menu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.abs-action-wrap-triangle{position:relative}.abs-action-wrap-triangle .action-default{width:100%}.abs-action-wrap-triangle .action-default:after,.abs-action-wrap-triangle .action-default:before{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.abs-action-wrap-triangle .action-default:active,.abs-action-wrap-triangle .action-default:focus,.abs-action-wrap-triangle .action-default:hover{box-shadow:none}._keyfocus .abs-action-wrap-triangle .action-default:focus{box-shadow:0 0 0 1px #007bdb}.ie10 .abs-action-wrap-triangle .action-default.disabled,.ie10 .abs-action-wrap-triangle .action-default[disabled],.ie9 .abs-action-wrap-triangle .action-default.disabled,.ie9 .abs-action-wrap-triangle .action-default[disabled]{background-color:#fcfcfc;opacity:1;text-shadow:none}.abs-action-wrap-triangle-right{display:inline-block;padding-right:1.6rem;position:relative}.abs-action-wrap-triangle-right .action-default:after,.abs-action-wrap-triangle-right .action-default:before{border-color:transparent transparent transparent #e3e3e3;border-width:1.7rem 0 1.6rem 1.7rem;left:100%;margin-left:-1.7rem}.abs-action-wrap-triangle-right .action-default:before{border-left-color:#949494;right:-1px}.abs-action-wrap-triangle-right .action-default:active:after,.abs-action-wrap-triangle-right .action-default:focus:after,.abs-action-wrap-triangle-right .action-default:hover:after{border-left-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-right .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-right .action-default[disabled]:after{border-color:transparent transparent transparent #fcfcfc}.abs-action-wrap-triangle-right .action-primary:after{border-color:transparent transparent transparent #eb5202}.abs-action-wrap-triangle-right .action-primary:active:after,.abs-action-wrap-triangle-right .action-primary:focus:after,.abs-action-wrap-triangle-right .action-primary:hover:after{border-left-color:#ba4000}.abs-action-wrap-triangle-left{display:inline-block;padding-left:1.6rem}.abs-action-wrap-triangle-left .action-default{text-indent:-.85rem}.abs-action-wrap-triangle-left .action-default:after,.abs-action-wrap-triangle-left .action-default:before{border-color:transparent #e3e3e3 transparent transparent;border-width:1.7rem 1.7rem 1.6rem 0;margin-right:-1.7rem;right:100%}.abs-action-wrap-triangle-left .action-default:before{border-right-color:#949494;left:-1px}.abs-action-wrap-triangle-left .action-default:active:after,.abs-action-wrap-triangle-left .action-default:focus:after,.abs-action-wrap-triangle-left .action-default:hover:after{border-right-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-left .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-left .action-default[disabled]:after{border-color:transparent #fcfcfc transparent transparent}.abs-action-wrap-triangle-left .action-primary:after{border-color:transparent #eb5202 transparent transparent}.abs-action-wrap-triangle-left .action-primary:active:after,.abs-action-wrap-triangle-left .action-primary:focus:after,.abs-action-wrap-triangle-left .action-primary:hover:after{border-right-color:#ba4000}.action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.action-default:active,.action-default:focus,.action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.action-primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.action-primary:active,.action-primary:focus,.action-primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-primary.disabled,.action-primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.action-secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.action-secondary:active,.action-secondary:focus,.action-secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-secondary:active{background-color:#35302c}.action-quaternary,.action-tertiary{background-color:transparent;border-color:transparent;text-shadow:none}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover,.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none}.action-tertiary{color:#008bdb}.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{color:#0fa7ff;text-decoration:underline}.action-quaternary{color:#333}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover{color:#1a1a1a}.action-close>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.action-close:before{content:'\e62f';transition:color .1s linear}.action-close:hover{cursor:pointer;text-decoration:none}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu .action-submenu .action-submenu._active,.abs-action-menu .action-submenu._active,.action-menu .action-submenu._active,.action-menu._active,.actions-split .action-menu .action-submenu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .dropdown-menu .action-submenu._active{display:block}.abs-action-menu .action-submenu .action-submenu>li,.abs-action-menu .action-submenu>li,.action-menu .action-submenu>li,.action-menu>li,.actions-split .action-menu .action-submenu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .dropdown-menu .action-submenu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu .action-submenu .action-submenu>li>a:hover,.abs-action-menu .action-submenu>li>a:hover,.action-menu .action-submenu>li>a:hover,.action-menu>li>a:hover,.actions-split .action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu>li>a:hover{text-decoration:none}.abs-action-menu .action-submenu .action-submenu>li._visible,.abs-action-menu .action-submenu .action-submenu>li:hover,.abs-action-menu .action-submenu>li._visible,.abs-action-menu .action-submenu>li:hover,.action-menu .action-submenu>li._visible,.action-menu .action-submenu>li:hover,.action-menu>li._visible,.action-menu>li:hover,.actions-split .action-menu .action-submenu .action-submenu>li._visible,.actions-split .action-menu .action-submenu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu>li:hover{background-color:#e3e3e3}.abs-action-menu .action-submenu .action-submenu>li:active,.abs-action-menu .action-submenu>li:active,.action-menu .action-submenu>li:active,.action-menu>li:active,.actions-split .action-menu .action-submenu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu>li:active{background-color:#cacaca}.abs-action-menu .action-submenu .action-submenu>li._parent,.abs-action-menu .action-submenu>li._parent,.action-menu .action-submenu>li._parent,.action-menu>li._parent,.actions-split .action-menu .action-submenu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.abs-action-menu .action-submenu>li._parent>.action-menu-item,.action-menu .action-submenu>li._parent>.action-menu-item,.action-menu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .item,.abs-action-menu .action-submenu .item,.action-menu .action-menu-item,.action-menu .action-submenu .action-menu-item,.action-menu .action-submenu .item,.action-menu .item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .item,.actions-split .action-menu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu .action-submenu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu .action-submenu,.ie9 .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .action-menu .action-submenu,.ie9 .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu .action-submenu .action-submenu a.action-menu-item,.abs-action-menu .action-submenu a.action-menu-item,.action-menu .action-submenu a.action-menu-item,.action-menu a.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu a.action-menu-item{color:#333}.abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.abs-action-menu .action-submenu a.action-menu-item:focus,.action-menu .action-submenu a.action-menu-item:focus,.action-menu a.action-menu-item:focus,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.messages .message:last-child{margin:0 0 2rem}.message{background:#fffbbb;border:none;border-radius:0;color:#333;font-size:1.4rem;margin:0 0 1px;padding:1.8rem 4rem 1.8rem 5.5rem;position:relative;text-shadow:none}.message:before{background:0 0;border:0;color:#007bdb;content:'\e61a';font-family:Icons;font-size:1.9rem;font-style:normal;font-weight:400;height:auto;left:1.9rem;line-height:inherit;margin-top:-1.3rem;position:absolute;speak:none;text-shadow:none;top:50%;width:auto}.message-notice:before{color:#007bdb;content:'\e61a'}.message-warning:before{color:#eb5202;content:'\e623'}.message-error{background:#fcc}.message-error:before{color:#e22626;content:'\e632';font-size:1.5rem;left:2.2rem;margin-top:-1rem}.message-success:before{color:#79a22e;content:'\e62d'}.message-spinner:before{display:none}.message-spinner .spinner{font-size:2.5rem;left:1.5rem;position:absolute;top:1.5rem}.message-in-rating-edit{margin-left:1.8rem;margin-right:1.8rem}.modal-popup .action-close,.modal-slide .action-close{color:#736963;position:absolute;right:0;top:0;z-index:1}.modal-popup .action-close:active,.modal-slide .action-close:active{-ms-transform:none;transform:none}.modal-popup .action-close:active:before,.modal-slide .action-close:active:before{font-size:1.8rem}.modal-popup .action-close:hover:before,.modal-slide .action-close:hover:before{color:#58504b}.modal-popup .action-close:before,.modal-slide .action-close:before{font-size:2rem}.modal-popup .action-close:focus,.modal-slide .action-close:focus{background-color:transparent}.modal-popup.prompt .prompt-message{padding:2rem 0}.modal-popup.prompt .prompt-message input{width:100%}.modal-popup.confirm .modal-inner-wrap .message,.modal-popup.prompt .modal-inner-wrap .message{background:#fff}.modal-popup.modal-system-messages .modal-inner-wrap{background:#fffbbb}.modal-popup._image-box .modal-inner-wrap{margin:5rem auto;max-width:78rem;position:static}.modal-popup._image-box .thumbnail-preview{padding-bottom:3rem;text-align:center}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image-block{border:1px solid #ccc;margin:0 auto 2rem;max-width:58rem;padding:2rem}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image{max-height:54rem}.modal-popup .modal-title{font-size:2.4rem;margin-right:6.4rem}.modal-popup .modal-footer{padding-top:2.6rem;text-align:right}.modal-popup .action-close{padding:3rem}.modal-popup .action-close:active,.modal-popup .action-close:focus{background:0 0;padding-right:3.1rem;padding-top:3.1rem}.modal-slide .modal-content-new-attribute{-webkit-overflow-scrolling:touch;overflow:auto;padding-bottom:0}.modal-slide .modal-content-new-attribute iframe{margin-bottom:-2.5rem}.modal-slide .modal-title{font-size:2.1rem;margin-right:5.7rem}.modal-slide .action-close{padding:2.1rem 2.6rem}.modal-slide .action-close:active{padding-right:2.7rem;padding-top:2.2rem}.modal-slide .page-main-actions{margin-bottom:.6rem;margin-top:2.1rem}.modal-slide .magento-message{padding:0 3rem 3rem;position:relative}.modal-slide .magento-message .insert-title-inner,.modal-slide .main-col .insert-title-inner{border-bottom:1px solid #adadad;margin:0 0 2rem;padding-bottom:.5rem}.modal-slide .magento-message .insert-actions,.modal-slide .main-col .insert-actions{float:right}.modal-slide .magento-message .title,.modal-slide .main-col .title{font-size:1.6rem;padding-top:.5rem}.modal-slide .main-col,.modal-slide .side-col{float:left;padding-bottom:0}.modal-slide .main-col:after,.modal-slide .side-col:after{display:none}.modal-slide .side-col{width:20%}.modal-slide .main-col{padding-right:0;width:80%}.modal-slide .content-footer .form-buttons{float:right}.modal-title{font-weight:400;margin-bottom:0;min-height:1em}.modal-title span{font-size:1.4rem;font-style:italic;margin-left:1rem}.spinner{display:inline-block;font-size:4rem;height:1em;margin-right:1.5rem;position:relative;width:1em}.spinner>span:nth-child(1){animation-delay:.27s;-ms-transform:rotate(-315deg);transform:rotate(-315deg)}.spinner>span:nth-child(2){animation-delay:.36s;-ms-transform:rotate(-270deg);transform:rotate(-270deg)}.spinner>span:nth-child(3){animation-delay:.45s;-ms-transform:rotate(-225deg);transform:rotate(-225deg)}.spinner>span:nth-child(4){animation-delay:.54s;-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.spinner>span:nth-child(5){animation-delay:.63s;-ms-transform:rotate(-135deg);transform:rotate(-135deg)}.spinner>span:nth-child(6){animation-delay:.72s;-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.spinner>span:nth-child(7){animation-delay:.81s;-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.spinner>span:nth-child(8){animation-delay:.9;-ms-transform:rotate(0deg);transform:rotate(0deg)}@keyframes fade{0%{background-color:#514943}100%{background-color:#fff}}.spinner>span{-ms-transform:scale(0.4);transform:scale(0.4);animation-name:fade;animation-duration:.72s;animation-iteration-count:infinite;animation-direction:linear;background-color:#fff;border-radius:6px;clip:rect(0 .28571429em .1em 0);height:.1em;margin-top:.5em;position:absolute;width:1em}.ie9 .spinner{background:url(../images/ajax-loader.gif) center no-repeat}.ie9 .spinner>span{display:none}.popup-loading{background:rgba(255,255,255,.8);border-color:#ef672f;color:#ef672f;font-size:14px;font-weight:700;left:50%;margin-left:-100px;padding:100px 0 10px;position:fixed;text-align:center;top:40%;width:200px;z-index:1003}.popup-loading:after{background-image:url(../images/loader-1.gif);content:'';height:64px;left:50%;margin:-32px 0 0 -32px;position:absolute;top:40%;width:64px;z-index:2}.loading-mask,.loading-old{background:rgba(255,255,255,.4);bottom:0;left:0;position:fixed;right:0;top:0;z-index:2003}.loading-mask img,.loading-old img{display:none}.loading-mask p,.loading-old p{margin-top:118px}.loading-mask .loader,.loading-old .loader{background:url(../images/loader-1.gif) 50% 30% no-repeat #f7f3eb;border-radius:5px;bottom:0;color:#575757;font-size:14px;font-weight:700;height:160px;left:0;margin:auto;opacity:.95;position:absolute;right:0;text-align:center;top:0;width:160px}.admin-user{float:right;line-height:1.36;margin-left:.3rem;z-index:490}.admin-user._active .admin__action-dropdown,.admin-user.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin-user .admin__action-dropdown{height:3.3rem;padding:.7rem 2.8rem .4rem 4rem}.admin-user .admin__action-dropdown._active:after,.admin-user .admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:after{border-color:#777 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.3rem;top:50%;transition:all .2s linear;width:0}._active .admin-user .admin__action-dropdown:after,.active .admin-user .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin-user .admin__action-dropdown:before{color:#777;content:'\e600';font-size:2rem;left:1.1rem;margin-top:-1.1rem;position:absolute;top:50%}.admin-user .admin__action-dropdown:hover:before{color:#333}.admin-user .admin__action-dropdown-menu{min-width:20rem;padding-left:1rem;padding-right:1rem}.admin-user .admin__action-dropdown-menu>li>a{padding-left:.5em;padding-right:1.8rem;transition:background-color .1s linear;white-space:nowrap}.admin-user .admin__action-dropdown-menu>li>a:hover{background-color:#e0f6fe;color:#333}.admin-user .admin__action-dropdown-menu>li>a:active{background-color:#c7effd;bottom:-1px;position:relative}.admin-user .admin__action-dropdown-menu .admin-user-name{text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:20rem;overflow:hidden;vertical-align:top}.admin-user-account-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:11.2rem}.search-global{float:right;margin-right:-.3rem;position:relative;z-index:480}.search-global-field{min-width:5rem}.search-global-field._active .search-global-input{background-color:#fff;border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);padding-right:4rem;width:25rem}.search-global-field._active .search-global-action{display:block;height:3.3rem;position:absolute;right:0;text-indent:-100%;top:0;width:5rem;z-index:3}.search-global-field .autocomplete-results{height:3.3rem;position:absolute;right:0;top:0;width:25rem}.search-global-field .search-global-menu{border:1px solid #007bdb;border-top-color:transparent;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin-top:-2px;padding:0;position:absolute;right:0;top:100%;z-index:2}.search-global-field .search-global-menu:after{background-color:#fff;content:'';height:5px;left:0;position:absolute;right:0;top:-5px}.search-global-field .search-global-menu>li{background-color:#fff;border-top:1px solid #ddd;display:block;font-size:1.2rem;padding:.75rem 1.4rem .55rem}.search-global-field .search-global-menu>li._active{background-color:#e0f6fe}.search-global-field .search-global-menu .title{display:block;font-size:1.4rem}.search-global-field .search-global-menu .type{color:#1a1a1a;display:block}.search-global-label{cursor:pointer;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;z-index:2}.search-global-label:active{-ms-transform:scale(0.9);transform:scale(0.9)}.search-global-label:hover:before{color:#000}.search-global-label:before{color:#777;content:'\e60c';font-size:2rem}.search-global-input{background-color:transparent;border:1px solid transparent;font-size:1.4rem;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;transition:all .1s linear,width .3s linear;width:5rem;z-index:1}.search-global-action{display:none}.notifications-wrapper{float:right;line-height:1;position:relative}.notifications-wrapper.active{z-index:500}.notifications-wrapper.active .notifications-action{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.notifications-wrapper.active .notifications-action:after{background-color:#fff;border:none;content:'';display:block;height:6px;left:-6px;margin-top:0;position:absolute;right:0;top:100%;width:auto}.notifications-wrapper .admin__action-dropdown-menu{padding:1rem 0 0;width:32rem}.notifications-action{color:#777;height:3.3rem;padding:.75rem 2rem .65rem}.notifications-action:after{display:none}.notifications-action:before{content:'\e607';font-size:1.9rem;margin-right:0}.notifications-action:active:before{position:relative;top:1px}.notifications-action .notifications-counter{background-color:#e22626;border-radius:1em;color:#fff;display:inline-block;font-size:1.1rem;font-weight:700;left:50%;margin-left:.3em;margin-top:-1.1em;padding:.3em .5em;position:absolute;top:50%}.notifications-entry{line-height:1.36;padding:.6rem 2rem .8rem;position:relative;transition:background-color .1s linear}.notifications-entry:hover{background-color:#e0f6fe}.notifications-entry.notifications-entry-last{margin:0 2rem;padding:.3rem 0 1.3rem;text-align:center}.notifications-entry.notifications-entry-last:hover{background-color:transparent}.notifications-entry+.notifications-entry-last{border-top:1px solid #ddd;padding-bottom:.6rem}.notifications-entry ._cutted{cursor:pointer}.notifications-entry ._cutted .notifications-entry-description-start:after{content:'...'}.notifications-entry-title{color:#ef672f;display:block;font-size:1.1rem;font-weight:700;margin-bottom:.7rem;margin-right:1em}.notifications-entry-description{color:#333;font-size:1.1rem;margin-bottom:.8rem}.notifications-entry-description-end{display:none}.notifications-entry-description-end._show{display:inline}.notifications-entry-time{color:#777;font-size:1.1rem}.notifications-close{line-height:1;padding:1rem;position:absolute;right:0;top:.6rem}.notifications-close:before{color:#ccc;content:'\e620';transition:color .1s linear}.notifications-close:hover:before{color:#b3b3b3}.notifications-close:active{-ms-transform:scale(0.95);transform:scale(0.95)}.page-header-actions{padding-top:1.1rem}.page-header-hgroup{padding-right:1.5rem}.page-title{color:#333;font-size:2.8rem}.page-header{padding:1.5rem 3rem}.menu-wrapper{display:inline-block;position:relative;width:8.8rem;z-index:700}.menu-wrapper:before{background-color:#373330;bottom:0;content:'';left:0;position:fixed;top:0;width:8.8rem;z-index:699}.menu-wrapper._fixed{left:0;position:fixed;top:0}.menu-wrapper._fixed~.page-wrapper{margin-left:8.8rem}.menu-wrapper .logo{display:block;height:8.8rem;padding:2.4rem 0 2.2rem;position:relative;text-align:center;z-index:700}._keyfocus .menu-wrapper .logo:focus{background-color:#4a4542;box-shadow:none}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a{background-color:#373330}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a:after{display:none}.menu-wrapper .logo:hover .logo-img{-webkit-filter:brightness(1.1);filter:brightness(1.1)}.menu-wrapper .logo:active .logo-img{-ms-transform:scale(0.95);transform:scale(0.95)}.menu-wrapper .logo .logo-img{height:4.2rem;transition:-webkit-filter .2s linear,filter .2s linear,transform .1s linear;width:3.5rem}.abs-menu-separator,.admin__menu .item-partners>a:after,.admin__menu .level-0:first-child>a:after{background-color:#736963;content:'';display:block;height:1px;left:0;margin-left:16%;position:absolute;top:0;width:68%}.admin__menu li{display:block}.admin__menu .level-0:first-child>a{position:relative}.admin__menu .level-0._active>a,.admin__menu .level-0:hover>a{color:#f7f3eb}.admin__menu .level-0._active>a{background-color:#524d49}.admin__menu .level-0:hover>a{background-color:#4a4542}.admin__menu .level-0>a{color:#aaa6a0;display:block;font-size:1rem;letter-spacing:.025em;min-height:6.2rem;padding:1.2rem .5rem .5rem;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;transition:background-color .1s linear;word-wrap:break-word;z-index:700}.admin__menu .level-0>a:focus{box-shadow:none}.admin__menu .level-0>a:before{content:'\e63a';display:block;font-size:2.2rem;height:2.2rem}.admin__menu .level-0>.submenu{background-color:#4a4542;box-shadow:0 0 3px #000;left:100%;min-height:calc(8.8rem + 2rem + 100%);padding:2rem 0 0;position:absolute;top:0;-ms-transform:translateX(-100%);transform:translateX(-100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;visibility:hidden;z-index:697}.ie10 .admin__menu .level-0>.submenu,.ie11 .admin__menu .level-0>.submenu{height:100%}.admin__menu .level-0._show>.submenu{-ms-transform:translateX(0);transform:translateX(0);visibility:visible;z-index:698}.admin__menu .level-1{margin-left:1.5rem;margin-right:1.5rem}.admin__menu [class*=level-]:not(.level-0) a{display:block;padding:1.25rem 1.5rem}.admin__menu [class*=level-]:not(.level-0) a:hover{background-color:#403934}.admin__menu [class*=level-]:not(.level-0) a:active{background-color:#322c29;padding-bottom:1.15rem;padding-top:1.35rem}.admin__menu .submenu li{min-width:23.8rem}.admin__menu .submenu a{color:#fcfcfc;transition:background-color .1s linear}.admin__menu .submenu a:focus,.admin__menu .submenu a:hover{box-shadow:none;text-decoration:none}._keyfocus .admin__menu .submenu a:focus{background-color:#403934}._keyfocus .admin__menu .submenu a:active{background-color:#322c29}.admin__menu .submenu .parent{margin-bottom:4.5rem}.admin__menu .submenu .parent .submenu-group-title{color:#a79d95;display:block;font-size:1.6rem;font-weight:600;margin-bottom:.7rem;padding:1.25rem 1.5rem;pointer-events:none}.admin__menu .submenu .column{display:table-cell}.admin__menu .submenu-title{color:#fff;display:block;font-size:2.2rem;font-weight:600;margin-bottom:4.2rem;margin-left:3rem;margin-right:5.8rem}.admin__menu .submenu-sub-title{color:#fff;display:block;font-size:1.2rem;margin:-3.8rem 5.8rem 3.8rem 3rem}.admin__menu .action-close{padding:2.4rem 2.8rem;position:absolute;right:0;top:0}.admin__menu .action-close:before{color:#a79d95;font-size:1.7rem}.admin__menu .action-close:hover:before{color:#fff}.admin__menu .item-dashboard>a:before{content:'\e604';font-size:1.8rem;padding-top:.4rem}.admin__menu .item-sales>a:before{content:'\e60b'}.admin__menu .item-catalog>a:before{content:'\e608'}.admin__menu .item-customer>a:before{content:'\e603';font-size:2.6rem;position:relative;top:-.4rem}.admin__menu .item-marketing>a:before{content:'\e609';font-size:2rem;padding-top:.2rem}.admin__menu .item-content>a:before{content:'\e602';font-size:2.4rem;position:relative;top:-.2rem}.admin__menu .item-report>a:before{content:'\e60a'}.admin__menu .item-stores>a:before{content:'\e60d';font-size:1.9rem;padding-top:.3rem}.admin__menu .item-system>a:before{content:'\e610'}.admin__menu .item-partners._active>a:after,.admin__menu .item-system._current+.item-partners>a:after{display:none}.admin__menu .item-partners>a{padding-bottom:1rem}.admin__menu .item-partners>a:before{content:'\e612'}.admin__menu .level-0>.submenu>ul>.level-1:only-of-type>.submenu-group-title,.admin__menu .submenu .column:only-of-type .submenu-group-title{display:none}.admin__menu-overlay{bottom:0;left:0;position:fixed;right:0;top:0;z-index:697}.store-switcher{color:#333;float:left;font-size:1.3rem;margin-top:.7rem}.store-switcher .admin__action-dropdown{background-color:#f8f8f8;margin-left:.5em}.store-switcher .dropdown{display:inline-block;position:relative}.store-switcher .dropdown:after,.store-switcher .dropdown:before{content:'';display:table}.store-switcher .dropdown:after{clear:both}.store-switcher .dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e607';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle:active:after,.store-switcher .dropdown .action.toggle:hover:after{color:#333}.store-switcher .dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle.active:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e618';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle.active:active:after,.store-switcher .dropdown .action.toggle.active:hover:after{color:#333}.store-switcher .dropdown .dropdown-menu{margin:4px 0 0;padding:0;list-style:none;background:#fff;border:1px solid #aaa6a0;min-width:19.5rem;z-index:100;box-sizing:border-box;display:none;position:absolute;top:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.store-switcher .dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .dropdown .dropdown-menu li:hover{background:0 0;cursor:pointer}.store-switcher .dropdown.active{overflow:visible}.store-switcher .dropdown.active .dropdown-menu{display:block}.store-switcher .dropdown-menu{left:0;margin-top:.5em;max-height:250px;overflow-y:auto;padding-top:.25em}.store-switcher .dropdown-menu li{border:0;cursor:default}.store-switcher .dropdown-menu li:hover{cursor:default}.store-switcher .dropdown-menu li a,.store-switcher .dropdown-menu li span{color:#333;display:block;padding:.5rem 1.3rem}.store-switcher .dropdown-menu li a{text-decoration:none}.store-switcher .dropdown-menu li a:hover{background:#e9e9e9}.store-switcher .dropdown-menu li span{color:#adadad;cursor:default}.store-switcher .dropdown-menu li.current span{background:#eee;color:#333}.store-switcher .dropdown-menu .store-switcher-store a,.store-switcher .dropdown-menu .store-switcher-store span{padding-left:2.6rem}.store-switcher .dropdown-menu .store-switcher-store-view a,.store-switcher .dropdown-menu .store-switcher-store-view span{padding-left:3.9rem}.store-switcher .dropdown-menu .dropdown-toolbar{border-top:1px solid #ebebeb;margin-top:1rem}.store-switcher .dropdown-menu .dropdown-toolbar a:before{content:'\e610';margin-right:.25em;position:relative;top:1px}.store-switcher-label{font-weight:700}.store-switcher-alt{display:inline-block;position:relative}.store-switcher-alt.active .dropdown-menu{display:block}.store-switcher-alt .dropdown-menu{margin-top:2px;white-space:nowrap}.store-switcher-alt .dropdown-menu ul{list-style:none;margin:0;padding:0}.store-switcher-alt strong{color:#a79d95;display:block;font-size:14px;font-weight:500;line-height:1.333;padding:5px 10px}.store-switcher-alt .store-selected{color:#676056;cursor:pointer;font-size:12px;font-weight:400;line-height:1.333}.store-switcher-alt .store-selected:after{-webkit-font-smoothing:antialiased;color:#afadac;content:'\e02c';font-style:normal;font-weight:400;margin:0 0 0 3px;speak:none;vertical-align:text-top}.store-switcher-alt .store-switcher-store,.store-switcher-alt .store-switcher-website{padding:0}.store-switcher-alt .store-switcher-store:hover,.store-switcher-alt .store-switcher-website:hover{background:0 0}.store-switcher-alt .manage-stores,.store-switcher-alt .store-switcher-all,.store-switcher-alt .store-switcher-store-view{padding:0}.store-switcher-alt .manage-stores>a,.store-switcher-alt .store-switcher-all>a{color:#676056;display:block;font-size:12px;padding:8px 15px;text-decoration:none}.store-switcher-website{margin:5px 0 0}.store-switcher-website>strong{padding-left:13px}.store-switcher-store{margin:1px 0 0}.store-switcher-store>strong{padding-left:20px}.store-switcher-store>ul{margin-top:1px}.store-switcher-store-view:first-child{border-top:1px solid #e5e5e5}.store-switcher-store-view>a{color:#333;display:block;font-size:13px;padding:5px 15px 5px 24px;text-decoration:none}.store-view:not(.store-switcher){float:left}.store-view .store-switcher-label{display:inline-block;margin-top:1rem}.tooltip{margin-left:.5em}.tooltip .help a,.tooltip .help span{cursor:pointer;display:inline-block;height:22px;position:relative;vertical-align:middle;width:22px;z-index:2}.tooltip .help a:before,.tooltip .help span:before{color:#333;content:'\e633';font-size:1.7rem}.tooltip .help a:hover{text-decoration:none}.tooltip .tooltip-content{background:#000;border-radius:3px;color:#fff;display:none;margin-left:-19px;margin-top:10px;max-width:200px;padding:4px 8px;position:absolute;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{border-bottom:5px solid #000;border-left:5px solid transparent;border-right:5px solid transparent;content:'';height:0;left:20px;opacity:.8;position:absolute;top:-5px;width:0}.tooltip .tooltip-content.loading{position:absolute}.tooltip .tooltip-content.loading:before{border-bottom-color:rgba(0,0,0,.3)}.tooltip:hover>.tooltip-content{display:block}.page-actions._fixed,.page-main-actions:not(._hidden){background:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;padding:1.5rem}.page-main-actions{margin:0 0 3rem}.page-main-actions._hidden .store-switcher{display:none}.page-main-actions._hidden .page-actions-placeholder{min-height:50px}.page-actions{float:right}.page-main-actions .page-actions._fixed{left:8.8rem;position:fixed;right:0;top:0;z-index:501}.page-main-actions .page-actions._fixed .page-actions-inner:before{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333;content:attr(data-title);float:left;font-size:2.8rem;margin-top:.3rem;max-width:50%}.page-actions .page-actions-buttons>button,.page-actions>button{float:right;margin-left:1.3rem}.page-actions .page-actions-buttons>button.action-back,.page-actions .page-actions-buttons>button.back,.page-actions>button.action-back,.page-actions>button.back{float:left;-ms-flex-order:-1;order:-1}.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before{content:'\e626';margin-right:.5em;position:relative;top:1px}.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary{-ms-flex-order:2;order:2}.page-actions .page-actions-buttons>button.save:not(.primary),.page-actions>button.save:not(.primary){-ms-flex-order:1;order:1}.page-actions .page-actions-buttons>button.delete,.page-actions>button.delete{-ms-flex-order:-1;order:-1}.page-actions .actions-split{float:right;margin-left:1.3rem;-ms-flex-order:2;order:2}.page-actions .actions-split .dropdown-menu .item{display:block}.page-actions-buttons{float:right;-ms-flex-pack:end;justify-content:flex-end;display:-ms-flexbox;display:flex}.customer-index-edit .page-actions-buttons{background-color:transparent}.admin__page-nav{background:#f1f1f1;border:1px solid #e3e3e3}.admin__page-nav._collapsed:first-child{border-bottom:none}.admin__page-nav._collapsed._show{border-bottom:1px solid #e3e3e3}.admin__page-nav._collapsed._show ._collapsible{background:#f1f1f1}.admin__page-nav._collapsed._show ._collapsible:after{content:'\e62b'}.admin__page-nav._collapsed._show ._collapsible+.admin__page-nav-items{display:block}.admin__page-nav._collapsed._hide .admin__page-nav-title-messages,.admin__page-nav._collapsed._hide .admin__page-nav-title-messages ._active{display:inline-block}.admin__page-nav+._collapsed{border-bottom:none;border-top:none}.admin__page-nav-title{border-bottom:1px solid #e3e3e3;color:#303030;display:block;font-size:1.4rem;line-height:1.2;margin:0 0 -1px;padding:1.8rem 1.5rem;position:relative;text-transform:uppercase}.admin__page-nav-title._collapsible{background:#fff;cursor:pointer;margin:0;padding-right:3.5rem;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-title._collapsible+.admin__page-nav-items{display:none;margin-top:-1px}.admin__page-nav-title._collapsible:after{content:'\e628';font-size:1.3rem;font-weight:700;position:absolute;right:1.8rem;top:2rem}.admin__page-nav-title._collapsible:hover{background:#f1f1f1}.admin__page-nav-title._collapsible:last-child{margin:0 0 -1px}.admin__page-nav-title strong{font-weight:700}.admin__page-nav-title .admin__page-nav-title-messages{display:none}.admin__page-nav-items{list-style-type:none;margin:0;padding:1rem 0 1.3rem}.admin__page-nav-item{border-left:3px solid transparent;margin-left:.7rem;padding:0;position:relative;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-item:hover{border-color:#e4e4e4}.admin__page-nav-item:hover .admin__page-nav-link{background:#e4e4e4;color:#303030;text-decoration:none}.admin__page-nav-item._active,.admin__page-nav-item.ui-state-active{border-color:#eb5202}.admin__page-nav-item._active .admin__page-nav-link,.admin__page-nav-item.ui-state-active .admin__page-nav-link{background:#fff;border-color:#e3e3e3;border-right:1px solid #fff;color:#303030;margin-right:-1px;font-weight:600}.admin__page-nav-item._loading:before,.admin__page-nav-item.ui-tabs-loading:before{display:none}.admin__page-nav-item._loading .admin__page-nav-item-message-loader,.admin__page-nav-item.ui-tabs-loading .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-link{border:1px solid transparent;border-width:1px 0;color:#303030;display:block;font-weight:500;line-height:1.2;margin:0 0 -1px;padding:2rem 4rem 2rem 1rem;transition:border-color .1s ease-out,background-color .1s ease-out;word-wrap:break-word}.admin__page-nav-item-messages{display:inline-block}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-size:1.4rem;font-weight:400;left:-1rem;line-height:1.36;padding:1.5rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after,.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf;margin-top:1px}.admin__page-nav-item-message-loader{display:none;margin-top:-1rem;position:absolute;right:0;top:50%}.admin__page-nav-item-message-loader .spinner{font-size:2rem;margin-right:1.5rem}._loading>.admin__page-nav-item-messages .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-item-message{position:relative}.admin__page-nav-item-message:hover{z-index:500}.admin__page-nav-item-message:hover .admin__page-nav-item-message-tooltip{display:block}.admin__page-nav-item-message._changed,.admin__page-nav-item-message._error{display:none}.admin__page-nav-item-message .admin__page-nav-item-message-icon{display:inline-block;font-size:1.4rem;padding-left:.8em;vertical-align:baseline}.admin__page-nav-item-message .admin__page-nav-item-message-icon:after{color:#666;content:'\e631'}._changed:not(._error)>.admin__page-nav-item-messages ._changed{display:inline-block}._error .admin__page-nav-item-message-icon:after{color:#eb5202;content:'\e623'}._error>.admin__page-nav-item-messages ._error{display:inline-block}._error>.admin__page-nav-item-messages ._error .spinner{font-size:2rem;margin-right:1.5rem}._error .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;left:-1rem;line-height:1.36;padding:2rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}._error .admin__page-nav-item-message-tooltip:after,._error .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}._error .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}._error .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf}.admin__data-grid-wrap-static .data-grid{box-sizing:border-box}.admin__data-grid-wrap-static .data-grid thead{color:#333}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td{background-color:#f5f5f5}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td._dragging{background-color:rgba(245,245,245,.95)}.admin__data-grid-wrap-static .data-grid ul{margin-left:1rem;padding-left:1rem}.admin__data-grid-wrap-static .admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-wrap-static .admin__data-grid-loading-mask .grid-loader{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-filters-actions-wrap{float:right}.data-grid-search-control-wrap{float:left;max-width:45.5rem;position:relative;width:35%}.data-grid-search-control-wrap :-ms-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-webkit-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-moz-placeholder{font-style:italic}.data-grid-search-control-wrap .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:.6rem 2rem .2rem;position:absolute;right:0;top:1px}.data-grid-search-control-wrap .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.data-grid-search-control-wrap .action-submit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.data-grid-search-control-wrap .action-submit:hover:before{color:#1a1a1a}._keyfocus .data-grid-search-control-wrap .action-submit:focus{box-shadow:0 0 0 1px #008bdb}.data-grid-search-control-wrap .action-submit:before{content:'\e60c';font-size:2rem;transition:color .1s linear}.data-grid-search-control-wrap .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.data-grid-search-control-wrap .abs-action-menu .action-submenu,.data-grid-search-control-wrap .abs-action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .action-menu,.data-grid-search-control-wrap .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:19.25rem;overflow-y:auto;z-index:398}.data-grid-search-control-wrap .action-menu-item._selected{background-color:#e0f6fe}.data-grid-search-control-wrap .data-grid-search-label{display:none}.data-grid-search-control{padding-right:6rem;width:100%}.data-grid-filters-action-wrap{float:left;padding-left:2rem}.data-grid-filters-action-wrap .action-default{font-size:1.3rem;margin-bottom:1rem;padding-left:1.7rem;padding-right:2.1rem;padding-top:.7rem}.data-grid-filters-action-wrap .action-default._active{background-color:#fff;border-bottom-color:#fff;border-right-color:#ccc;font-weight:600;margin:-.1rem 0 0;padding-bottom:1.6rem;padding-top:.8rem;position:relative;z-index:281}.data-grid-filters-action-wrap .action-default._active:after{background-color:#eb5202;bottom:100%;content:'';height:3px;left:-1px;position:absolute;right:-1px}.data-grid-filters-action-wrap .action-default:before{color:#333;content:'\e605';font-size:1.8rem;margin-right:.4rem;position:relative;top:-1px;vertical-align:top}.data-grid-filters-action-wrap .filters-active{display:none}.admin__action-grid-select .admin__control-select{margin:-.5rem .5rem 0 0;padding-bottom:.6rem;padding-top:.6rem}.admin__data-grid-filters-wrap{opacity:0;visibility:hidden;clear:both;font-size:1.3rem;transition:opacity .3s ease}.admin__data-grid-filters-wrap._show{opacity:1;visibility:visible;border-bottom:1px solid #ccc;border-top:1px solid #ccc;margin-bottom:.7rem;padding:3.6rem 0 3rem;position:relative;top:-1px;z-index:280}.admin__data-grid-filters-wrap._show .admin__data-grid-filters,.admin__data-grid-filters-wrap._show .admin__data-grid-filters-footer{display:block}.admin__data-grid-filters-wrap .admin__form-field-label,.admin__data-grid-filters-wrap .admin__form-field-legend{display:block;font-weight:700;margin:0 0 .3rem;text-align:left}.admin__data-grid-filters-wrap .admin__form-field{display:inline-block;margin-bottom:2em;margin-left:0;padding-left:2rem;padding-right:2rem;vertical-align:top;width:calc(100% / 4 - 4px)}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field{display:block;float:none;margin-bottom:1.5rem;padding-left:0;padding-right:0;width:auto}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field:last-child{margin-bottom:0}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-label{border:1px solid transparent;float:left;font-weight:400;line-height:1.36;margin-bottom:0;padding-bottom:.6rem;padding-right:1em;padding-top:.6rem;width:25%}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-control{margin-left:25%}.admin__data-grid-filters-wrap .admin__action-multiselect,.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text,.admin__data-grid-filters-wrap .admin__form-field-label{font-size:1.3rem}.admin__data-grid-filters-wrap .admin__control-select{height:3.2rem;padding-top:.5rem}.admin__data-grid-filters-wrap .admin__action-multiselect:before{height:3.2rem;width:3.2rem}.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text._has-datepicker{width:100%}.admin__data-grid-filters{display:none;margin-left:-2rem;margin-right:-2rem}.admin__filters-legend{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-filters-footer{display:none;font-size:1.4rem}.admin__data-grid-filters-footer .admin__footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-filters-footer .admin__footer-secondary-actions{float:left;width:50%}.admin__data-grid-filters-current{border-bottom:.1rem solid #ccc;border-top:.1rem solid #ccc;display:none;font-size:1.3rem;margin-bottom:.9rem;padding-bottom:.8rem;padding-top:1.1rem;width:100%}.admin__data-grid-filters-current._show{display:table;position:relative;top:-1px;z-index:3}.admin__data-grid-filters-current._show+.admin__data-grid-filters-wrap._show{margin-top:-1rem}.admin__current-filters-actions-wrap,.admin__current-filters-list-wrap,.admin__current-filters-title-wrap{display:table-cell;vertical-align:top}.admin__current-filters-title{margin-right:1em;white-space:nowrap}.admin__current-filters-list-wrap{width:100%}.admin__current-filters-list{margin-bottom:0}.admin__current-filters-list>li{display:inline-block;font-weight:600;margin:0 1rem .5rem;padding-right:2.6rem;position:relative}.admin__current-filters-list .action-remove{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0;line-height:1;position:absolute;right:0;top:1px}.admin__current-filters-list .action-remove:hover{background-color:transparent;border:none;box-shadow:none}.admin__current-filters-list .action-remove:hover:before{color:#949494}.admin__current-filters-list .action-remove:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__current-filters-list .action-remove:before{color:#adadad;content:'\e620';font-size:1.6rem;transition:color .1s linear}.admin__current-filters-list .action-remove>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__current-filters-actions-wrap .action-clear{border:none;padding-bottom:0;padding-top:0;white-space:nowrap}.admin__data-grid-pager-wrap{float:right;text-align:right}.admin__data-grid-pager{display:inline-block;margin-left:3rem}.admin__data-grid-pager .admin__control-text::-webkit-inner-spin-button,.admin__data-grid-pager .admin__control-text::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.admin__data-grid-pager .admin__control-text{-moz-appearance:textfield;text-align:center;width:4.4rem}.action-next,.action-previous{width:4.4rem}.action-next:before,.action-previous:before{font-weight:700}.action-next>span,.action-previous>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-previous{margin-right:2.5rem;text-indent:-.25em}.action-previous:before{content:'\e629'}.action-next{margin-left:1.5rem;text-indent:.1em}.action-next:before{content:'\e62a'}.admin__data-grid-action-bookmarks{opacity:.98}.admin__data-grid-action-bookmarks .admin__action-dropdown-text:after{left:0;right:-6px}.admin__data-grid-action-bookmarks._active{z-index:290}.admin__data-grid-action-bookmarks .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:15rem;min-width:4.9rem;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown:before{content:'\e60f'}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu{font-size:1.3rem;left:0;padding:1rem 0;right:auto}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li{padding:0 5rem 0 0;position:relative;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action){transition:background-color .1s linear}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action):hover{background-color:#e3e3e3}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item{max-width:23rem;min-width:18rem;white-space:normal;word-break:break-all}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit{display:none;padding-bottom:1rem;padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit .action-dropdown-menu-item-actions{padding-bottom:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action{padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action+.action-dropdown-menu-item-last{padding-top:.5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a{color:#008bdb;text-decoration:none;display:inline-block;padding-left:1.1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a:hover{color:#0fa7ff;text-decoration:underline}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-last{padding-bottom:0}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item{display:none}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item-edit{display:block}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._active .action-dropdown-menu-link{font-weight:600}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{font-size:1.3rem;min-width:15rem;width:calc(100% - 4rem)}.ie9 .admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{width:15rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-actions{border-left:1px solid #fff;bottom:0;position:absolute;right:0;top:0;width:5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-link{color:#333;display:block;text-decoration:none;padding:1rem 1rem 1rem 2.1rem}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit,.admin__data-grid-action-bookmarks .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;vertical-align:top}.admin__data-grid-action-bookmarks .action-delete:hover,.admin__data-grid-action-bookmarks .action-edit:hover,.admin__data-grid-action-bookmarks .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before{font-size:1.7rem}.admin__data-grid-action-bookmarks .action-delete>span,.admin__data-grid-action-bookmarks .action-edit>span,.admin__data-grid-action-bookmarks .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit{padding:.6rem 1.4rem}.admin__data-grid-action-bookmarks .action-delete:active,.admin__data-grid-action-bookmarks .action-edit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__data-grid-action-bookmarks .action-submit{padding:.6rem 1rem .6rem .8rem}.admin__data-grid-action-bookmarks .action-submit:active{position:relative;right:-1px}.admin__data-grid-action-bookmarks .action-submit:before{content:'\e625'}.admin__data-grid-action-bookmarks .action-delete:before{content:'\e630'}.admin__data-grid-action-bookmarks .action-edit{padding-top:.8rem}.admin__data-grid-action-bookmarks .action-edit:before{content:'\e631'}.admin__data-grid-action-columns._active{opacity:.98;z-index:290}.admin__data-grid-action-columns .admin__action-dropdown:before{content:'\e610';font-size:1.8rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-columns-menu{color:#303030;font-size:1.3rem;overflow:hidden;padding:2.2rem 3.5rem 1rem;z-index:1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-header{border-bottom:1px solid #d1d1d1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-content{width:49.2rem}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-footer{border-top:1px solid #d1d1d1;padding-top:2.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content{max-height:22.85rem;overflow-y:auto;padding-top:1.5rem;position:relative;width:47.4rem}.admin__data-grid-action-columns-menu .admin__field-option{float:left;height:1.9rem;margin-bottom:1.5rem;padding:0 1rem 0 0;width:15.8rem}.admin__data-grid-action-columns-menu .admin__field-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-header{padding-bottom:1.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-footer{padding:1rem 0 2rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-secondary-actions{float:left;margin-left:-1em}.admin__data-grid-action-export._active{opacity:.98;z-index:290}.admin__data-grid-action-export .admin__action-dropdown:before{content:'\e635';font-size:1.7rem;left:.3rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-export-menu{padding-left:2rem;padding-right:2rem;padding-top:1rem}.admin__data-grid-action-export-menu .admin__action-dropdown-footer-main-actions{padding-bottom:2rem;padding-top:2.5rem;white-space:nowrap}.sticky-header{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:8.8rem;margin-top:-1px;padding:.5rem 3rem 0;position:fixed;right:0;top:77px;z-index:398}.sticky-header .admin__data-grid-wrap{margin-bottom:0;overflow-x:visible;padding-bottom:0}.sticky-header .admin__data-grid-header-row{position:relative;text-align:right}.sticky-header .admin__data-grid-header-row:last-child{margin:0}.sticky-header .admin__data-grid-actions-wrap,.sticky-header .admin__data-grid-filters-wrap,.sticky-header .admin__data-grid-pager-wrap,.sticky-header .data-grid-filters-actions-wrap,.sticky-header .data-grid-search-control-wrap{display:inline-block;float:none;vertical-align:top}.sticky-header .action-select-wrap{float:left;margin-right:1.5rem;width:16.66666667%}.sticky-header .admin__control-support-text{float:left}.sticky-header .data-grid-search-control-wrap{margin:-.5rem 0 0 1.1rem;width:auto}.sticky-header .data-grid-search-control-wrap .data-grid-search-label{box-sizing:border-box;cursor:pointer;display:block;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;position:relative;text-align:center}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before{color:#333;content:'\e60c';font-size:2rem;transition:color .1s linear}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:hover:before{color:#000}.sticky-header .data-grid-search-control-wrap .data-grid-search-label span{display:none}.sticky-header .data-grid-filters-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-left:0;position:relative}.sticky-header .data-grid-filters-actions-wrap .action-default{background-color:transparent;border:1px solid transparent;box-sizing:border-box;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;text-align:center;transition:all .15s ease}.sticky-header .data-grid-filters-actions-wrap .action-default span{display:none}.sticky-header .data-grid-filters-actions-wrap .action-default:before{margin:0}.sticky-header .data-grid-filters-actions-wrap .action-default._active{background-color:#fff;border-color:#adadad #adadad #fff;box-shadow:1px 1px 5px rgba(0,0,0,.5);z-index:210}.sticky-header .data-grid-filters-actions-wrap .action-default._active:after{background-color:#fff;content:'';height:6px;left:-2px;position:absolute;right:-6px;top:100%}.sticky-header .data-grid-filters-action-wrap{padding:0}.sticky-header .admin__data-grid-filters-wrap{background-color:#fff;border:1px solid #adadad;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:0;padding-left:3.5rem;padding-right:3.5rem;position:absolute;top:100%;width:100%;z-index:209}.sticky-header .admin__data-grid-filters-current+.admin__data-grid-filters-wrap._show{margin-top:-6px}.sticky-header .filters-active{background-color:#e04f00;border-radius:10px;color:#fff;display:block;font-size:1.4rem;font-weight:700;padding:.1rem .7rem;position:absolute;right:-7px;top:0;z-index:211}.sticky-header .filters-active:empty{padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-right:.3rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown{background-color:transparent;box-sizing:border-box;min-width:3.8rem;padding-left:.6rem;padding-right:.6rem;text-align:center}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:0;min-width:0;overflow:hidden}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:before{margin:0}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap{margin-right:1.1rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after,.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:after{display:none}.sticky-header .admin__data-grid-actions-wrap ._active .admin__action-dropdown{background-color:#fff}.sticky-header .admin__data-grid-action-bookmarks .admin__action-dropdown:before{position:relative;top:-3px}.sticky-header .admin__data-grid-filters-current{border-bottom:0;border-top:0;margin-bottom:0;padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-pager .admin__control-text,.sticky-header .admin__data-grid-pager-wrap .admin__control-support-text,.sticky-header .data-grid-search-control-wrap .action-submit,.sticky-header .data-grid-search-control-wrap .data-grid-search-control{display:none}.sticky-header .action-next{margin:0}.sticky-header .data-grid{margin-bottom:-1px}.data-grid-cap-left,.data-grid-cap-right{background-color:#f8f8f8;bottom:-2px;position:absolute;top:6rem;width:3rem;z-index:201}.data-grid-cap-left{left:0}.admin__data-grid-header{font-size:1.4rem}.admin__data-grid-header-row+.admin__data-grid-header-row{margin-top:1.1rem}.admin__data-grid-header-row:last-child{margin-bottom:0}.admin__data-grid-header-row .action-select-wrap{display:block}.admin__data-grid-header-row .action-select{width:100%}.admin__data-grid-actions-wrap{float:right;margin-left:1.1rem;margin-top:-.5rem;text-align:right}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap{position:relative;text-align:left;vertical-align:middle}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._hide+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:first-child:after{display:none}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown-menu{border-color:#adadad}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after{border-left:1px solid #ccc;content:'';height:3.2rem;left:0;position:absolute;top:.5rem;z-index:3}.admin__data-grid-actions-wrap .admin__action-dropdown{padding-bottom:1.7rem;padding-top:1.2rem}.admin__data-grid-actions-wrap .admin__action-dropdown:after{margin-top:-.4rem}.admin__data-grid-outer-wrap{min-height:8rem;position:relative}.admin__data-grid-wrap{margin-bottom:2rem;max-width:100%;overflow-x:auto;padding-bottom:1rem;padding-top:2rem}.admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-loading-mask .spinner{font-size:4rem;left:50%;margin-left:-2rem;margin-top:-2rem;position:absolute;top:50%}.ie9 .admin__data-grid-loading-mask .spinner{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-cell-content{display:inline-block;overflow:hidden;width:100%}body._in-resize{cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body._in-resize *,body._in-resize .data-grid-th,body._in-resize .data-grid-th._draggable,body._in-resize .data-grid-th._sortable{cursor:col-resize!important}._layout-fixed{table-layout:fixed}.data-grid{border:none;font-size:1.3rem;margin-bottom:0;width:100%}.data-grid:not(._dragging-copy) ._odd-row td._dragging{background-color:#d0d0d0}.data-grid:not(._dragging-copy) ._dragging{background-color:#d9d9d9;color:rgba(48,48,48,.95)}.data-grid:not(._dragging-copy) ._dragging a{color:rgba(0,139,219,.95)}.data-grid:not(._dragging-copy) ._dragging a:hover{color:rgba(15,167,255,.95)}.data-grid._dragged{outline:#007bdb solid 1px}.data-grid thead{background-color:transparent}.data-grid tfoot th{padding:1rem}.data-grid tr._odd-row td{background-color:#f5f5f5}.data-grid tr._odd-row td._update-status-active{background:#89e1ff}.data-grid tr._odd-row td._update-status-upcoming{background:#b7ee63}.data-grid tr:hover td._update-status-active,.data-grid tr:hover td._update-status-upcoming{background-color:#e5f7fe}.data-grid tr.data-grid-tr-no-data td{font-size:1.6rem;padding:3rem;text-align:center}.data-grid tr.data-grid-tr-no-data:hover td{background-color:#fff;cursor:default}.data-grid tr:active td{background-color:#e0f6fe}.data-grid tr:hover td{background-color:#e5f7fe}.data-grid tr._dragged td{background:#d0d0d0}.data-grid tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.data-grid tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.data-grid tr:not(.data-grid-editable-row):last-child td{border-bottom:.1rem solid #d6d6d6}.data-grid tr ._clickable,.data-grid tr._clickable{cursor:pointer}.data-grid tr._disabled{pointer-events:none}.data-grid td,.data-grid th{font-size:1.3rem;line-height:1.36;transition:background-color .1s linear;vertical-align:top}.data-grid td._resizing,.data-grid th._resizing{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid td._hidden,.data-grid th._hidden{display:none}.data-grid td._fit,.data-grid th._fit{width:1%}.data-grid td{background-color:#fff;border-left:.1rem dashed #d6d6d6;border-right:.1rem dashed #d6d6d6;color:#303030;padding:1rem}.data-grid td:first-child{border-left-style:solid}.data-grid td:last-child{border-right-style:solid}.data-grid td .action-select-wrap{position:static}.data-grid td .action-select{color:#008bdb;text-decoration:none;background-color:transparent;border:none;font-size:1.3rem;padding:0 3rem 0 0;position:relative}.data-grid td .action-select:hover{color:#0fa7ff;text-decoration:underline}.data-grid td .action-select:hover:after{border-color:#0fa7ff transparent transparent}.data-grid td .action-select:after{border-color:#008bdb transparent transparent;margin:.6rem 0 0 .7rem;right:auto;top:auto}.data-grid td .action-select:before{display:none}.data-grid td .abs-action-menu .action-submenu,.data-grid td .abs-action-menu .action-submenu .action-submenu,.data-grid td .action-menu,.data-grid td .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:10rem;right:0;text-align:left;top:auto;z-index:1}.data-grid td._update-status-active{background:#bceeff}.data-grid td._update-status-upcoming{background:#ccf391}.data-grid th{background-color:#514943;border:.1rem solid #8a837f;border-left-color:transparent;color:#fff;font-weight:600;padding:0;text-align:left}.data-grid th:first-child{border-left-color:#8a837f}.data-grid th._dragover-left{box-shadow:inset 3px 0 0 0 #fff;z-index:2}.data-grid th._dragover-right{box-shadow:inset -3px 0 0 0 #fff}.data-grid .shadow-div{cursor:col-resize;height:100%;margin-right:-5px;position:absolute;right:0;top:0;width:10px}.data-grid .data-grid-th{background-clip:padding-box;color:#fff;padding:1rem;position:relative;vertical-align:middle}.data-grid .data-grid-th._resize-visible .shadow-div{cursor:auto;display:none}.data-grid .data-grid-th._draggable{cursor:grab}.data-grid .data-grid-th._sortable{cursor:pointer;transition:background-color .1s linear;z-index:1}.data-grid .data-grid-th._sortable:focus,.data-grid .data-grid-th._sortable:hover{background-color:#5f564f}.data-grid .data-grid-th._sortable:active{padding-bottom:.9rem;padding-top:1.1rem}.data-grid .data-grid-th.required>span:after{color:#f38a5e;content:'*';margin-left:.3rem}.data-grid .data-grid-checkbox-cell{overflow:hidden;padding:0;vertical-align:top;width:5.2rem}.data-grid .data-grid-checkbox-cell:hover{cursor:default}.data-grid .data-grid-thumbnail-cell{text-align:center;width:7rem}.data-grid .data-grid-thumbnail-cell img{border:1px solid #d6d6d6;width:5rem}.data-grid .data-grid-multicheck-cell{padding:1rem 1rem .9rem;text-align:center;vertical-align:middle}.data-grid .data-grid-onoff-cell{text-align:center;width:12rem}.data-grid .data-grid-actions-cell{padding-left:2rem;padding-right:2rem;text-align:center;width:1%}.data-grid._hidden{display:none}.data-grid._dragging-copy{box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;opacity:.95;position:fixed;top:0;z-index:1000}.data-grid._dragging-copy .data-grid-th{border:1px solid #007bdb;border-bottom:none}.data-grid._dragging-copy .data-grid-th,.data-grid._dragging-copy .data-grid-th._sortable{cursor:grabbing}.data-grid._dragging-copy tr:last-child td{border-bottom:1px solid #007bdb}.data-grid._dragging-copy td{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:rgba(255,251,230,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td,.data-grid._dragging-copy._in-edit .data-grid-editable-row:hover td{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:after,.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{left:0;right:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:only-child{border-left:1px solid #007bdb;border-right:1px solid #007bdb;left:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-select,.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-text{opacity:.5}.data-grid .data-grid-controls-row td{padding-top:1.6rem}.data-grid .data-grid-controls-row td.data-grid-checkbox-cell{padding-top:.6rem}.data-grid .data-grid-controls-row td [class*=admin__control-],.data-grid .data-grid-controls-row td button{margin-top:-1.7rem}.data-grid._in-edit tr:hover td{background-color:#e6e6e6}.data-grid._in-edit ._odd-row.data-grid-editable-row td,.data-grid._in-edit ._odd-row.data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit ._odd-row td,.data-grid._in-edit ._odd-row:hover td{background-color:#dcdcdc}.data-grid._in-edit .data-grid-editable-row-actions td,.data-grid._in-edit .data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid._in-edit td{background-color:#e6e6e6;pointer-events:none}.data-grid._in-edit .data-grid-checkbox-cell{pointer-events:auto}.data-grid._in-edit .data-grid-editable-row{border:.1rem solid #adadad;border-bottom-color:#c2c2c2}.data-grid._in-edit .data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit .data-grid-editable-row td{background-color:#fff;border-bottom-color:#fff;border-left-style:hidden;border-right-style:hidden;border-top-color:#fff;pointer-events:auto;vertical-align:middle}.data-grid._in-edit .data-grid-editable-row td:first-child{border-left-color:#adadad;border-left-style:solid}.data-grid._in-edit .data-grid-editable-row td:first-child:after,.data-grid._in-edit .data-grid-editable-row td:first-child:before{left:0}.data-grid._in-edit .data-grid-editable-row td:last-child{border-right-color:#adadad;border-right-style:solid;left:-.1rem}.data-grid._in-edit .data-grid-editable-row td:last-child:after,.data-grid._in-edit .data-grid-editable-row td:last-child:before{right:0}.data-grid._in-edit .data-grid-editable-row .admin__control-select,.data-grid._in-edit .data-grid-editable-row .admin__control-text{width:100%}.data-grid._in-edit .data-grid-bulk-edit-panel td{vertical-align:bottom}.data-grid .data-grid-editable-row td{border-left-color:#fff;border-left-style:solid;position:relative;z-index:1}.data-grid .data-grid-editable-row td:after{bottom:0;box-shadow:0 5px 5px rgba(0,0,0,.25);content:'';height:.9rem;left:0;margin-top:-1rem;position:absolute;right:0}.data-grid .data-grid-editable-row td:before{background-color:#fff;bottom:0;content:'';height:1rem;left:-10px;position:absolute;right:-10px;z-index:1}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td,.data-grid .data-grid-editable-row.data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:first-child{border-left-color:#fff;border-right-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:last-child{left:0}.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:#fffbe6}.data-grid .data-grid-editable-row-actions{left:50%;margin-left:-12.5rem;margin-top:-2px;position:absolute;text-align:center}.data-grid .data-grid-editable-row-actions td{width:25rem}.data-grid .data-grid-editable-row-actions [class*=action-]{min-width:9rem}.data-grid .data-grid-draggable-row-cell{width:1%}.data-grid .data-grid-draggable-row-cell .draggable-handle{padding:0}.data-grid-th._sortable._ascend,.data-grid-th._sortable._descend{padding-right:2.7rem}.data-grid-th._sortable._ascend:before,.data-grid-th._sortable._descend:before{margin-top:-1em;position:absolute;right:1rem;top:50%}.data-grid-th._sortable._ascend:before{content:'\2193'}.data-grid-th._sortable._descend:before{content:'\2191'}.data-grid-checkbox-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:right}.data-grid-checkbox-cell-inner:hover{cursor:pointer}.data-grid-state-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:center}.data-grid-state-cell-inner>span{display:inline-block;font-style:italic;padding:.6rem 0}.data-grid-row-parent._active>td .data-grid-checkbox-cell-inner:before{content:'\e62b'}.data-grid-row-parent>td .data-grid-checkbox-cell-inner{padding-left:3.7rem;position:relative}.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before{content:'\e628';font-size:1rem;font-weight:700;left:1.35rem;position:absolute;top:1.6rem}.data-grid-th._col-xs{width:1%}.data-grid-info-panel{box-shadow:0 0 5px rgba(0,0,0,.5);margin:2rem .1rem -2rem}.data-grid-info-panel .messages{overflow:hidden}.data-grid-info-panel .messages .message{margin:1rem}.data-grid-info-panel .messages .message:last-child{margin-bottom:1rem}.data-grid-info-panel-actions{padding:1rem;text-align:right}.data-grid-editable-row .admin__field-control{position:relative}.data-grid-editable-row .admin__field-control._error:after{border-color:transparent #ee7d7d transparent transparent;border-style:solid;border-width:0 12px 12px 0;content:'';position:absolute;right:0;top:0}.data-grid-editable-row .admin__field-control._error .admin__control-text{border-color:#ee7d7d}.data-grid-editable-row .admin__field-control._focus:after{display:none}.data-grid-editable-row .admin__field-error{bottom:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin:0 auto 1.5rem;max-width:32rem;position:absolute;right:0}.data-grid-editable-row .admin__field-error:after,.data-grid-editable-row .admin__field-error:before{border-style:solid;content:'';left:50%;position:absolute;top:100%}.data-grid-editable-row .admin__field-error:after{border-color:#fffbbb transparent transparent;border-width:10px 10px 0;margin-left:-10px;z-index:1}.data-grid-editable-row .admin__field-error:before{border-color:#ee7d7d transparent transparent;border-width:11px 12px 0;margin-left:-12px}.data-grid-bulk-edit-panel .admin__field-label-vertical{display:block;font-size:1.2rem;margin-bottom:.5rem;text-align:left}.data-grid-row-changed{cursor:default;display:block;opacity:.5;position:relative;width:100%;z-index:1}.data-grid-row-changed:after{content:'\e631';display:inline-block}.data-grid-row-changed .data-grid-row-changed-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:100%;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;line-height:1.36;margin-bottom:1.5rem;padding:1rem;position:absolute;right:-1rem;text-transform:none;width:27rem;word-break:normal;z-index:2}.data-grid-row-changed._changed{opacity:1;z-index:3}.data-grid-row-changed._changed:hover .data-grid-row-changed-tooltip{display:block}.data-grid-row-changed._changed:hover:before{background:#f1f1f1;border:1px solid #f1f1f1;bottom:100%;box-shadow:4px 4px 3px -1px rgba(0,0,0,.15);content:'';display:block;height:1.6rem;left:50%;margin:0 0 .7rem -.8rem;position:absolute;-ms-transform:rotate(45deg);transform:rotate(45deg);width:1.6rem;z-index:3}.ie9 .data-grid-row-changed._changed:hover:before{display:none}.admin__data-grid-outer-wrap .data-grid-checkbox-cell{overflow:hidden}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner{position:relative}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner:before{bottom:0;content:'';height:500%;left:0;position:absolute;right:0;top:0}.admin__data-grid-wrap-static .data-grid-checkbox-cell:hover{cursor:pointer}.admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:1.1rem 1.8rem .9rem;padding:0}.adminhtml-cms-hierarchy-index .admin__data-grid-wrap-static .data-grid-actions-cell:first-child{padding:0}.adminhtml-export-index .admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:0;padding:1.1rem 1.8rem 1.9rem}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before,.admin__control-file-label:before,.admin__control-multiselect,.admin__control-select,.admin__control-text,.admin__control-textarea,.selectmenu{-webkit-appearance:none;background-color:#fff;border:1px solid #adadad;border-radius:1px;box-shadow:none;color:#303030;font-size:1.4rem;font-weight:400;height:auto;line-height:1.36;padding:.6rem 1rem;transition:border-color .1s linear;vertical-align:baseline;width:auto}.admin__control-addon [class*=admin__control-][class]:hover~[class*=admin__addon-]:last-child:before,.admin__control-multiselect:hover,.admin__control-select:hover,.admin__control-text:hover,.admin__control-textarea:hover,.selectmenu:hover,.selectmenu:hover .selectmenu-toggle:before{border-color:#878787}.admin__control-addon [class*=admin__control-][class]:focus~[class*=admin__addon-]:last-child:before,.admin__control-file:active+.admin__control-file-label:before,.admin__control-file:focus+.admin__control-file-label:before,.admin__control-multiselect:focus,.admin__control-select:focus,.admin__control-text:focus,.admin__control-textarea:focus,.selectmenu._focus,.selectmenu._focus .selectmenu-toggle:before{border-color:#007bdb;box-shadow:none;outline:0}.admin__control-addon [class*=admin__control-][class][disabled]~[class*=admin__addon-]:last-child:before,.admin__control-file[disabled]+.admin__control-file-label:before,.admin__control-multiselect[disabled],.admin__control-select[disabled],.admin__control-text[disabled],.admin__control-textarea[disabled]{background-color:#e9e9e9;border-color:#adadad;color:#303030;cursor:not-allowed;opacity:.5}.admin__field-row[class]>.admin__field-control,.admin__fieldset>.admin__field.admin__field-wide[class]>.admin__field-control{clear:left;float:none;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label{display:block;line-height:1.4rem;margin-bottom:.86rem;margin-top:-.14rem;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label:before,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label:before{display:none}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span{padding-left:1.5rem}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span:after{left:0;margin-left:30px}.admin__legend{font-size:1.8rem;font-weight:600;margin-bottom:3rem}.admin__control-checkbox,.admin__control-radio{cursor:pointer;opacity:.01;overflow:hidden;position:absolute;vertical-align:top}.admin__control-checkbox:after,.admin__control-radio:after{display:none}.admin__control-checkbox+label,.admin__control-radio+label{cursor:pointer;display:inline-block}.admin__control-checkbox+label:before,.admin__control-radio+label:before{background-color:#fff;border:1px solid #adadad;color:transparent;float:left;height:1.6rem;text-align:center;vertical-align:top;width:1.6rem}.admin__control-checkbox+.admin__field-label,.admin__control-radio+.admin__field-label{padding-left:2.6rem}.admin__control-checkbox+.admin__field-label:before,.admin__control-radio+.admin__field-label:before{margin:1px 1rem 0 -2.6rem}.admin__control-checkbox:checked+label:before,.admin__control-radio:checked+label:before{color:#514943}.admin__control-checkbox.disabled+label,.admin__control-checkbox[disabled]+label,.admin__control-radio.disabled+label,.admin__control-radio[disabled]+label{color:#303030;cursor:default;opacity:.5}.admin__control-checkbox.disabled+label:before,.admin__control-checkbox[disabled]+label:before,.admin__control-radio.disabled+label:before,.admin__control-radio[disabled]+label:before{background-color:#e9e9e9;border-color:#adadad;cursor:default}._keyfocus .admin__control-checkbox:not(.disabled):focus+label:before,._keyfocus .admin__control-checkbox:not([disabled]):focus+label:before,._keyfocus .admin__control-radio:not(.disabled):focus+label:before,._keyfocus .admin__control-radio:not([disabled]):focus+label:before{border-color:#007bdb}.admin__control-checkbox:not(.disabled):hover+label:before,.admin__control-checkbox:not([disabled]):hover+label:before,.admin__control-radio:not(.disabled):hover+label:before,.admin__control-radio:not([disabled]):hover+label:before{border-color:#878787}.admin__control-radio+label:before{border-radius:1.6rem;content:'';transition:border-color .1s linear,color .1s ease-in}.admin__control-radio.admin__control-radio+label:before{line-height:140%}.admin__control-radio:checked+label{position:relative}.admin__control-radio:checked+label:after{background-color:#514943;border-radius:50%;content:'';height:10px;left:3px;position:absolute;top:4px;width:10px}.admin__control-radio:checked:not(.disabled):hover,.admin__control-radio:checked:not(.disabled):hover+label,.admin__control-radio:checked:not([disabled]):hover,.admin__control-radio:checked:not([disabled]):hover+label{cursor:default}.admin__control-radio:checked:not(.disabled):hover+label:before,.admin__control-radio:checked:not([disabled]):hover+label:before{border-color:#adadad}.admin__control-checkbox+label:before{border-radius:1px;content:'';font-size:0;transition:font-size .1s ease-out,color .1s ease-out,border-color .1s linear}.admin__control-checkbox:checked+label:before{content:'\e62d';font-size:1.1rem;line-height:125%}.admin__control-checkbox:not(:checked)._indeterminate+label:before,.admin__control-checkbox:not(:checked):indeterminate+label:before{color:#514943;content:'-';font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700}input[type=checkbox].admin__control-checkbox,input[type=radio].admin__control-checkbox{margin:0;position:absolute}.admin__control-text{min-width:4rem}.admin__control-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#adadad,#adadad);background-position:calc(100% - 12px) -34px,100%,calc(100% - 3.2rem) 0;background-size:auto,3.2rem 100%,1px 100%;background-repeat:no-repeat;max-width:100%;min-width:8.5rem;padding-bottom:.5rem;padding-right:4.4rem;padding-top:.5rem;transition:border-color .1s linear}.admin__control-select:hover{border-color:#878787;cursor:pointer}.admin__control-select:focus{background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#007bdb,#007bdb);background-position:calc(100% - 12px) 13px,100%,calc(100% - 3.2rem) 0;border-color:#007bdb}.admin__control-select::-ms-expand{display:none}.ie9 .admin__control-select{background-image:none;padding-right:1rem}option:empty{display:none}.admin__control-multiselect{height:auto;max-width:100%;min-width:15rem;overflow:auto;padding:0;resize:both}.admin__control-multiselect optgroup,.admin__control-multiselect option{padding:.5rem 1rem}.admin__control-file-wrapper{display:inline-block;padding:.5rem 1rem;position:relative;z-index:1}.admin__control-file-label:before{content:'';left:0;position:absolute;top:0;width:100%;z-index:0}.admin__control-file{background:0 0;border:0;padding-top:.7rem;position:relative;width:auto;z-index:1}.admin__control-support-text{border:1px solid transparent;display:inline-block;font-size:1.4rem;line-height:1.36;padding-bottom:.6rem;padding-top:.6rem}.admin__control-support-text+[class*=admin__control-],[class*=admin__control-]+.admin__control-support-text{margin-left:.7rem}.admin__control-service{float:left;margin:.8rem 0 0 3rem}.admin__control-textarea{height:8.48rem;line-height:1.18;padding-top:.8rem;resize:vertical}.admin__control-addon{-ms-flex-direction:row;flex-direction:row;display:inline-flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;position:relative;width:100%;z-index:1}.admin__control-addon>[class*=admin__addon-],.admin__control-addon>[class*=admin__control-]{-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;position:relative;z-index:1}.admin__control-addon .admin__control-select{width:auto}.admin__control-addon .admin__control-text{margin:.1rem;padding:.5rem .9rem;width:100%}.admin__control-addon [class*=admin__control-][class]{appearence:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-order:1;order:1;-ms-flex-negative:1;flex-shrink:1;background-color:transparent;border-color:transparent;box-shadow:none;vertical-align:top}.admin__control-addon [class*=admin__control-][class]+[class*=admin__control-]{border-left-color:#adadad}.admin__control-addon [class*=admin__control-][class] :focus{box-shadow:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child{padding-left:1rem;position:static!important;z-index:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child>*{position:relative;vertical-align:top;z-index:1}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:empty{padding:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before{bottom:0;box-sizing:border-box;content:'';left:0;position:absolute;top:0;width:100%;z-index:-1}.admin__addon-prefix,.admin__addon-suffix{border:0;box-sizing:border-box;color:#858585;display:inline-block;font-size:1.4rem;font-weight:400;height:3.2rem;line-height:3.2rem;padding:0}.admin__addon-suffix{-ms-flex-order:3;order:3}.admin__addon-suffix:last-child{padding-right:1rem}.admin__addon-prefix{-ms-flex-order:0;order:0}.ie9 .admin__control-addon:after{clear:both;content:'';display:block;height:0;overflow:hidden}.ie9 .admin__addon{min-width:0;overflow:hidden;text-align:right;white-space:nowrap;width:auto}.ie9 .admin__addon [class*=admin__control-]{display:inline}.ie9 .admin__addon-prefix{float:left}.ie9 .admin__addon-suffix{float:right}.admin__control-collapsible{width:100%}.admin__control-collapsible ._dragged .admin__collapsible-block-wrapper .admin__collapsible-title{background:#d0d0d0}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before,.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{background:#008bdb;content:'';display:block;height:3px;left:0;position:absolute;right:0}.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{top:-3px}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before{bottom:-3px}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper{border:0;margin:0;position:relative}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper .fieldset-wrapper-title{background:#f8f8f8;border:2px solid #ccc}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title{font-size:1.4rem;font-weight:400;line-height:1;padding:1.6rem 4rem 1.6rem 3.8rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title:before{left:1rem;right:auto;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding:0;position:absolute;right:1rem;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before{content:'\e630';font-size:2rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete>span{display:none}.admin__control-collapsible .admin__collapsible-content{background-color:#fff;margin-bottom:1rem}.admin__control-collapsible .admin__collapsible-content>.fieldset-wrapper{border:1px solid #ccc;margin-top:-1px;padding:1rem}.admin__control-collapsible .admin__collapsible-content .admin__fieldset{padding:0}.admin__control-collapsible .admin__collapsible-content .admin__field:last-child{margin-bottom:0}.admin__control-table-wrapper{max-width:100%;overflow-x:auto;overflow-y:hidden}.admin__control-table{width:100%}.admin__control-table thead{background-color:transparent}.admin__control-table tbody td{vertical-align:top}.admin__control-table tfoot th{padding-bottom:1.3rem}.admin__control-table tfoot th.validation{padding-bottom:0;padding-top:0}.admin__control-table tfoot td{border-top:1px solid #fff}.admin__control-table tfoot .admin__control-table-pagination{float:right;padding-bottom:0}.admin__control-table tfoot .action-previous{margin-right:.5rem}.admin__control-table tfoot .action-next{margin-left:.9rem}.admin__control-table tr:last-child td{border-bottom:none}.admin__control-table tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.admin__control-table tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.admin__control-table tr._dragged td,.admin__control-table tr._dragged th{background:#d0d0d0}.admin__control-table td,.admin__control-table th{background-color:#efefef;border:0;border-bottom:1px solid #fff;padding:1.3rem 1rem 1.3rem 0;text-align:left;vertical-align:top}.admin__control-table td:first-child,.admin__control-table th:first-child{padding-left:1rem}.admin__control-table td>.admin__control-select,.admin__control-table td>.admin__control-text,.admin__control-table th>.admin__control-select,.admin__control-table th>.admin__control-text{width:100%}.admin__control-table td._hidden,.admin__control-table th._hidden{display:none}.admin__control-table td._fit,.admin__control-table th._fit{width:1px}.admin__control-table th{color:#303030;font-size:1.4rem;font-weight:600;vertical-align:bottom}.admin__control-table th._required span:after{color:#eb5202;content:'*'}.admin__control-table .control-table-actions-th{white-space:nowrap}.admin__control-table .control-table-actions-cell{padding-top:1.8rem;text-align:center;width:1%}.admin__control-table .control-table-options-th{text-align:center;width:10rem}.admin__control-table .control-table-options-cell{text-align:center}.admin__control-table .control-table-text{line-height:3.2rem}.admin__control-table .col-draggable{padding-top:2.2rem;width:1%}.admin__control-table .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.admin__control-table .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-table .action-delete:before{content:'\e630';font-size:2rem}.admin__control-table .action-delete>span{display:none}.admin__control-table .draggable-handle{padding:0}.admin__control-table._dragged{outline:#007bdb solid 1px}.admin__control-table-action{background-color:#efefef;border-top:1px solid #fff;padding:1.3rem 1rem}.admin__dynamic-rows._dragged{opacity:.95;position:absolute;z-index:999}.admin__dynamic-rows.admin__control-table .admin__control-fields>.admin__field{border:0;padding:0}.admin__dynamic-rows td>.admin__field{border:0;margin:0;padding:0}.admin__control-table-pagination{padding-bottom:1rem}.admin__control-table-pagination .admin__data-grid-pager{float:right}.admin__field-tooltip{display:inline-block;margin-top:.5rem;max-width:45px;overflow:visible;vertical-align:top;width:0}.admin__field-tooltip:hover{position:relative;z-index:500}.admin__field-option .admin__field-tooltip{margin-top:.5rem}.admin__field-tooltip .admin__field-tooltip-action{margin-left:2rem;position:relative;z-index:2;display:inline-block;text-decoration:none}.admin__field-tooltip .admin__field-tooltip-action:before{-webkit-font-smoothing:antialiased;font-size:2.2rem;line-height:1;color:#514943;content:'\e633';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.admin__field-tooltip .admin__control-text:focus+.admin__field-tooltip-content,.admin__field-tooltip:hover .admin__field-tooltip-content{display:block}.admin__field-tooltip .admin__field-tooltip-content{bottom:3.8rem;display:none;right:-2.3rem}.admin__field-tooltip .admin__field-tooltip-content:after,.admin__field-tooltip .admin__field-tooltip-content:before{border:1.6rem solid transparent;height:0;width:0;border-top-color:#afadac;content:'';display:block;position:absolute;right:2rem;top:100%;z-index:3}.admin__field-tooltip .admin__field-tooltip-content:after{border-top-color:#fffbbb;margin-top:-1px;z-index:4}.abs-admin__field-tooltip-content,.admin__field-tooltip .admin__field-tooltip-content{box-shadow:0 2px 8px 0 rgba(0,0,0,.3);background:#fffbbb;border:1px solid #afadac;border-radius:1px;padding:1.5rem 2.5rem;position:absolute;width:32rem;z-index:1}.admin__field-fallback-reset{font-size:1.25rem;white-space:nowrap;width:30px}.admin__field-fallback-reset>span{margin-left:.5rem;position:relative}.admin__field-fallback-reset:active{-ms-transform:scale(0.98);transform:scale(0.98)}.admin__field-fallback-reset:before{transition:color .1s linear;content:'\e642';font-size:1.3rem;margin-left:.5rem}.admin__field-fallback-reset:hover{cursor:pointer;text-decoration:none}.admin__field-fallback-reset:focus{background:0 0}.abs-field-size-x-small,.abs-field-sizes.admin__field-x-small>.admin__field-control,.admin__field.admin__field-x-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-x-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-x-small>.admin__field-control{width:8rem}.abs-field-size-small,.abs-field-sizes.admin__field-small>.admin__field-control,.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control,.admin__field.admin__field-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-small>.admin__field-control{width:15rem}.abs-field-size-medium,.abs-field-sizes.admin__field-medium>.admin__field-control,.admin__field.admin__field-medium>.admin__field-control,.admin__fieldset>.admin__field.admin__field-medium>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-medium>.admin__field-control{width:34rem}.abs-field-size-large,.abs-field-sizes.admin__field-large>.admin__field-control,.admin__field.admin__field-large>.admin__field-control,.admin__fieldset>.admin__field.admin__field-large>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-large>.admin__field-control{width:64rem}.abs-field-no-label,.admin__field-group-additional,.admin__field-no-label,.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-control{margin-left:calc((100%) * .25 + 30px)}.admin__fieldset{border:0;margin:0;min-width:0;padding:0}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title{padding-left:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title strong{font-size:1.7rem;font-weight:600}.admin__fieldset .fieldset-wrapper.admin__fieldset-section .admin__fieldset-wrapper-content>.admin__fieldset{padding-top:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section:last-child .admin__fieldset-wrapper-content>.admin__fieldset{padding-bottom:0}.admin__fieldset>.admin__field{border:0;margin:0 0 0 -30px;padding:0}.admin__fieldset>.admin__field:after{clear:both;content:'';display:table}.admin__fieldset>.admin__field>.admin__field-control{width:calc((100%) * .5 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-label{display:none}.admin__fieldset>.admin__field+.admin__field._empty._no-header{margin-top:-3rem}.admin__fieldset-product-websites{position:relative;z-index:300}.admin__fieldset-note{margin-bottom:2rem}.admin__form-field{border:0;margin:0;padding:0}.admin__field-control .admin__control-text,.admin__field-control .admin__control-textarea,.admin__form-field-control .admin__control-text,.admin__form-field-control .admin__control-textarea{width:100%}.admin__field-label{color:#303030;cursor:pointer;margin:0;text-align:right}.admin__field-label+br{display:none}.admin__field:not(.admin__field-option)>.admin__field-label{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:3.2rem;padding:0;white-space:nowrap}.admin__field:not(.admin__field-option)>.admin__field-label:before{opacity:0;visibility:hidden;content:'.';margin-left:-7px;overflow:hidden}.admin__field:not(.admin__field-option)>.admin__field-label span{display:inline-block;line-height:1.2;vertical-align:middle;white-space:normal}.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]{position:relative}._required>.admin__field-label>span:after,.required>.admin__field-label>span:after{color:#eb5202;content:'*';display:inline-block;font-size:1.6rem;font-weight:500;line-height:1;margin-left:10px;margin-top:.2rem;position:absolute;z-index:1}._disabled>.admin__field-label{color:#999;cursor:default}.admin__field{margin-bottom:0}.admin__field+.admin__field{margin-top:1.5rem}.admin__field:not(.admin__field-option)~.admin__field-option{margin-top:.5rem}.admin__field.admin__field-option~.admin__field-option{margin-top:.9rem}.admin__field~.admin__field-option:last-child{margin-bottom:.8rem}.admin__fieldset>.admin__field{margin-bottom:3rem;position:relative}.admin__field legend.admin__field-label{opacity:0}.admin__field[data-config-scope]:before{color:gray;content:attr(data-config-scope);display:inline-block;font-size:1.2rem;left:calc((100%) * .75 - 30px);line-height:3.2rem;margin-left:60px;position:absolute;width:calc((100%) * .25 - 30px)}.admin__field-control .admin__field[data-config-scope]:nth-child(n+2):before{content:''}.admin__field._error .admin__field-control [class*=admin__addon-]:before,.admin__field._error .admin__field-control [class*=admin__control-] [class*=admin__addon-]:before,.admin__field._error .admin__field-control>[class*=admin__control-]{border-color:#e22626}.admin__field._disabled,.admin__field._disabled:hover{box-shadow:inherit;cursor:inherit;opacity:1;outline:inherit}.admin__field._hidden{display:none}.admin__field-control+.admin__field-control{margin-top:1.5rem}.admin__field-control._with-tooltip>.admin__control-addon,.admin__field-control._with-tooltip>.admin__control-select,.admin__field-control._with-tooltip>.admin__control-text,.admin__field-control._with-tooltip>.admin__control-textarea,.admin__field-control._with-tooltip>.admin__field-option{max-width:calc(100% - 45px - 4px)}.admin__field-control._with-tooltip .admin__field-tooltip{width:auto}.admin__field-control._with-tooltip .admin__field-option{display:inline-block}.admin__field-control._with-reset>.admin__control-addon,.admin__field-control._with-reset>.admin__control-text,.admin__field-control._with-reset>.admin__control-textarea{width:calc(100% - 30px - .5rem - 4px)}.admin__field-control._with-reset .admin__field-fallback-reset{margin-left:.5rem;margin-top:1rem;vertical-align:top}.admin__field-control._with-reset._with-tooltip>.admin__control-addon,.admin__field-control._with-reset._with-tooltip>.admin__control-text,.admin__field-control._with-reset._with-tooltip>.admin__control-textarea{width:calc(100% - 30px - .5rem - 45px - 8px)}.admin__fieldset>.admin__field-collapsible{margin-bottom:0}.admin__fieldset>.admin__field-collapsible .admin__field-control{border-top:1px solid #ccc;display:block;font-size:1.7rem;font-weight:700;padding:1.7rem 0;width:calc(97%)}.admin__fieldset>.admin__field-collapsible .admin__field-option{padding-top:0}.admin__field-collapsible+div{margin-top:2.5rem}.admin__field-collapsible .admin__control-radio+label:before{height:1.8rem;width:1.8rem}.admin__field-collapsible .admin__control-radio:checked+label:after{left:4px;top:5px}.admin__field-error{background:#fffbbb;border:1px solid #ee7d7d;box-sizing:border-box;color:#555;display:block;font-size:1.2rem;font-weight:400;line-height:1.2;margin:.2rem 0 0;padding:.8rem 1rem .9rem}.admin__field-note{color:#303030;font-size:1.2rem;margin:10px 0 0;padding:0}.admin__additional-info{padding-top:1rem}.admin__field-option{padding-top:.7rem}.admin__field-option .admin__field-label{text-align:left}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2),.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1){display:inline-block}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option{display:inline-block;margin-left:41px;margin-top:0}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option:before,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option:before{background:#cacaca;content:'';display:inline-block;height:20px;margin-left:-20px;position:absolute;width:1px}.admin__field-value{display:inline-block;padding-top:.7rem}.admin__field-service{padding-top:1rem}.admin__control-fields>.admin__field:first-child,[class*=admin__control-grouped]>.admin__field:first-child{position:static}.admin__control-fields>.admin__field:first-child>.admin__field-label,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px;background:#fff;cursor:pointer;left:0;position:absolute;top:0}.admin__control-fields>.admin__field:first-child>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label span:before{display:block}.admin__control-fields>.admin__field._disabled>.admin__field-label,[class*=admin__control-grouped]>.admin__field._disabled>.admin__field-label{cursor:default}.admin__control-fields>.admin__field>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field>.admin__field-label span:before{display:none}.admin__control-fields .admin__field-label~.admin__field-control{width:100%}.admin__control-fields .admin__field-option{padding-top:0}[class*=admin__control-grouped]{box-sizing:border-box;display:table;width:100%}[class*=admin__control-grouped]>.admin__field{display:table-cell;vertical-align:top}[class*=admin__control-grouped]>.admin__field>.admin__field-control{float:none;width:100%}[class*=admin__control-grouped]>.admin__field.admin__field-default,[class*=admin__control-grouped]>.admin__field.admin__field-large,[class*=admin__control-grouped]>.admin__field.admin__field-medium,[class*=admin__control-grouped]>.admin__field.admin__field-small,[class*=admin__control-grouped]>.admin__field.admin__field-x-small{width:1px}[class*=admin__control-grouped]>.admin__field.admin__field-default+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-large+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-medium+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-small+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-x-small+.admin__field:last-child{width:auto}[class*=admin__control-grouped]>.admin__field:nth-child(n+2){padding-left:20px}.admin__control-group-equal{table-layout:fixed}.admin__control-group-equal>.admin__field{width:50%}.admin__field-control-group{margin-top:.8rem}.admin__field-control-group>.admin__field{padding:0}.admin__control-grouped-date>.admin__field-date{white-space:nowrap;width:1px}.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control{float:left;position:relative}.admin__control-grouped-date>.admin__field-date+.admin__field:last-child{width:auto}.admin__control-grouped-date>.admin__field-date+.admin__field-date>.admin__field-label{float:left;padding-right:20px}.admin__control-grouped-date .ui-datepicker-trigger{left:100%;top:0}.admin__field-group-columns.admin__field-control.admin__control-grouped{width:calc((100%) * 1 - 30px);float:left;margin-left:30px}.admin__field-group-columns>.admin__field:first-child>.admin__field-label{float:none;margin:0;opacity:1;position:static;text-align:left}.admin__field-group-columns .admin__control-select{width:100%}.admin__field-group-additional{clear:both}.admin__field-group-additional .action-advanced{margin-top:1rem}.admin__field-group-additional .action-secondary{width:100%}.admin__field-group-show-label{white-space:nowrap}.admin__field-group-show-label>.admin__field-control,.admin__field-group-show-label>.admin__field-label{display:inline-block;vertical-align:top}.admin__field-group-show-label>.admin__field-label{margin-right:20px}.admin__field-complex{margin:1rem 0 3rem;padding-left:1rem}.admin__field:not(._hidden)+.admin__field-complex{margin-top:3rem}.admin__field-complex .admin__field-complex-title{clear:both;color:#303030;font-size:1.7rem;font-weight:600;letter-spacing:.025em;margin-bottom:1rem}.admin__field-complex .admin__field-complex-elements{float:right;max-width:40%}.admin__field-complex .admin__field-complex-elements button{margin-left:1rem}.admin__field-complex .admin__field-complex-content{max-width:60%;overflow:hidden}.admin__field-complex .admin__field-complex-text{margin-left:-1rem}.admin__field-complex+.admin__field._empty._no-header{margin-top:-3rem}.admin__legend{float:left;position:static;width:100%}.admin__legend+br{clear:left;display:block;height:0;overflow:hidden}.message{margin-bottom:3rem}.message-icon-top:before{margin-top:0;top:1.8rem}.nav{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;display:none;margin-bottom:3rem;padding:2.2rem 1.5rem 0 0}.nav .btn-group,.nav-bar-outer-actions{float:right;margin-bottom:1.7rem}.nav .btn-group .btn-wrap,.nav-bar-outer-actions .btn-wrap{float:right;margin-left:.5rem;margin-right:.5rem}.nav .btn-group .btn-wrap .btn,.nav-bar-outer-actions .btn-wrap .btn{padding-left:.5rem;padding-right:.5rem}.nav-bar-outer-actions{margin-top:-10.6rem;padding-right:1.5rem}.btn-wrap-try-again{width:9.5rem}.btn-wrap-next,.btn-wrap-prev{width:8.5rem}.nav-bar{counter-reset:i;float:left;margin:0 1rem 1.7rem 0;padding:0;position:relative;white-space:nowrap}.nav-bar:before{background-color:#d4d4d4;background-repeat:repeat-x;background-image:linear-gradient(to bottom,#d1d1d1 0,#d4d4d4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#d1d1d1', endColorstr='#d4d4d4', GradientType=0);border-bottom:1px solid #d9d9d9;border-top:1px solid #bfbfbf;content:'';height:1rem;left:5.15rem;position:absolute;right:5.15rem;top:.7rem}.nav-bar>li{display:inline-block;font-size:0;position:relative;vertical-align:top;width:10.3rem}.nav-bar>li:first-child:after{display:none}.nav-bar>li:after{background-color:#514943;content:'';height:.5rem;left:calc(-50% + .25rem);position:absolute;right:calc(50% + .7rem);top:.9rem}.nav-bar>li.disabled:before,.nav-bar>li.ui-state-disabled:before{bottom:0;content:'';left:0;position:absolute;right:0;top:0;z-index:1}.nav-bar>li.active~li:after,.nav-bar>li.ui-state-active~li:after{display:none}.nav-bar>li.active~li a:after,.nav-bar>li.ui-state-active~li a:after{background-color:transparent;border-color:transparent;color:#a6a6a6}.nav-bar>li.active a,.nav-bar>li.ui-state-active a{color:#000}.nav-bar>li.active a:hover,.nav-bar>li.ui-state-active a:hover{cursor:default}.nav-bar>li.active a:after,.nav-bar>li.ui-state-active a:after{background-color:#fff;content:''}.nav-bar a{color:#514943;display:block;font-size:1.2rem;font-weight:600;line-height:1.2;overflow:hidden;padding:3rem .5em 0;position:relative;text-align:center;text-overflow:ellipsis}.nav-bar a:hover{text-decoration:none}.nav-bar a:after{background-color:#514943;border:.4rem solid #514943;border-radius:100%;color:#fff;content:counter(i);counter-increment:i;height:1.5rem;left:50%;line-height:.6;margin-left:-.8rem;position:absolute;right:auto;text-align:center;top:.4rem;width:1.5rem}.nav-bar a:before{background-color:#d6d6d6;border:1px solid transparent;border-bottom-color:#d9d9d9;border-radius:100%;border-top-color:#bfbfbf;content:'';height:2.3rem;left:50%;line-height:1;margin-left:-1.2rem;position:absolute;top:0;width:2.3rem}.tooltip{display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.19rem;font-weight:400;line-height:1.4;opacity:0;position:absolute;visibility:visible;z-index:10}.tooltip.in{opacity:.9}.tooltip.top{margin-top:-4px;padding:8px 0}.tooltip.right{margin-left:4px;padding:0 8px}.tooltip.bottom{margin-top:4px;padding:8px 0}.tooltip.left{margin-left:-4px;padding:0 8px}.tooltip p:last-child{margin-bottom:0}.tooltip-inner{background-color:#fff;border:1px solid #adadad;border-radius:0;box-shadow:1px 1px 1px #ccc;color:#41362f;max-width:31rem;padding:.5em 1em;text-decoration:none}.tooltip-arrow,.tooltip-arrow:after{border:solid transparent;height:0;position:absolute;width:0}.tooltip-arrow:after{content:'';position:absolute}.tooltip.top .tooltip-arrow,.tooltip.top .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:50%;margin-left:-8px}.tooltip.top-left .tooltip-arrow,.tooltip.top-left .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;margin-bottom:-8px;right:8px}.tooltip.top-right .tooltip-arrow,.tooltip.top-right .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:8px;margin-bottom:-8px}.tooltip.right .tooltip-arrow,.tooltip.right .tooltip-arrow:after{border-right-color:#949494;border-width:8px 8px 8px 0;left:1px;margin-top:-8px;top:50%}.tooltip.right .tooltip-arrow:after{border-right-color:#fff;border-width:6px 7px 6px 0;margin-left:0;margin-top:-6px}.tooltip.left .tooltip-arrow,.tooltip.left .tooltip-arrow:after{border-left-color:#949494;border-width:8px 0 8px 8px;margin-top:-8px;right:0;top:50%}.tooltip.bottom .tooltip-arrow,.tooltip.bottom .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:50%;margin-left:-8px;top:0}.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;margin-top:-8px;right:8px;top:0}.tooltip.bottom-right .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:8px;margin-top:-8px;top:0}.password-strength{display:block;margin:0 -.3rem 1em;white-space:nowrap}.password-strength.password-strength-too-short .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child+.password-strength-item{background-color:#e22626}.password-strength.password-strength-fair .password-strength-item:first-child,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item+.password-strength-item{background-color:#ef672f}.password-strength.password-strength-good .password-strength-item:first-child,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item+.password-strength-item,.password-strength.password-strength-strong .password-strength-item{background-color:#79a22e}.password-strength .password-strength-item{background-color:#ccc;display:inline-block;font-size:0;height:1.4rem;margin-right:.3rem;width:calc(20% - .6rem)}@keyframes progress-bar-stripes{from{background-position:4rem 0}to{background-position:0 0}}.progress{background-color:#fafafa;border:1px solid #ccc;clear:left;height:3rem;margin-bottom:3rem;overflow:hidden}.progress-bar{background-color:#79a22e;color:#fff;float:left;font-size:1.19rem;height:100%;line-height:3rem;text-align:center;transition:width .6s ease;width:0}.progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.progress-bar-text-description{margin-bottom:1.6rem}.progress-bar-text-progress{text-align:right}.page-columns .page-inner-sidebar{margin:0 0 3rem}.page-header{margin-bottom:2.7rem;padding-bottom:2rem;position:relative}.page-header:before{border-bottom:1px solid #e3e3e3;bottom:0;content:'';display:block;height:1px;left:3rem;position:absolute;right:3rem}.container .page-header:before{content:normal}.page-header .message{margin-bottom:1.8rem}.page-header .message+.message{margin-top:-1.5rem}.page-header .admin__action-dropdown,.page-header .search-global-input{transition:none}.container .page-header{margin-bottom:0}.page-title-wrapper{margin-top:1.1rem}.container .page-title-wrapper{background:url(../../pub/images/logo.svg) no-repeat;min-height:41px;padding:4px 0 0 45px}.admin__menu .level-0:first-child>a{margin-top:1.6rem}.admin__menu .level-0:first-child>a:after{top:-1.6rem}.admin__menu .level-0:first-child._active>a:after{display:block}.admin__menu .level-0>a{padding-bottom:1.3rem;padding-top:1.3rem}.admin__menu .level-0>a:before{margin-bottom:.7rem}.admin__menu .item-home>a:before{content:'\e611';font-size:2.3rem;padding-top:-.1rem}.admin__menu .item-component>a:before{content:'\e612'}.admin__menu .item-extension>a:before{content:'\e647'}.admin__menu .item-upgrade>a:before{content:'\e614'}.admin__menu .item-system-config>a:before{content:'\e610'}.admin__menu .item-tools>a:before{content:'\e613'}.modal-sub-title{font-size:1.7rem;font-weight:600}.modal-connect-signin .modal-inner-wrap{max-width:80rem}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{-webkit-overflow-scrolling:touch;bottom:0;box-sizing:border-box;left:0;overflow:auto;position:fixed;right:0;top:0;z-index:999}.ngdialog *,.ngdialog:after,.ngdialog:before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation *{animation:none!important}.ngdialog.ngdialog-closing .ngdialog-content,.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-animation:ngdialog-fadeout .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadeout .5s}.ngdialog-overlay{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s;background:rgba(0,0,0,.4);bottom:0;left:0;position:fixed;right:0;top:0}.ngdialog-content{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s}body.ngdialog-open{overflow:hidden}.component-indicator{border-radius:50%;cursor:help;display:inline-block;height:16px;text-align:center;vertical-align:middle;width:16px}.component-indicator::after,.component-indicator::before{background:#fff;display:block;opacity:0;position:absolute;transition:opacity .2s linear .1s;visibility:hidden}.component-indicator::before{border:1px solid #adadad;border-radius:1px;box-shadow:0 0 2px rgba(0,0,0,.4);content:attr(data-label);font-size:1.2rem;margin:30px 0 0 -10px;min-width:50px;padding:4px 5px}.component-indicator::after{border-color:#999;border-style:solid;border-width:1px 0 0 1px;box-shadow:-1px -1px 1px rgba(0,0,0,.1);content:'';height:10px;margin:9px 0 0 5px;-ms-transform:rotate(45deg);transform:rotate(45deg);width:10px}.component-indicator:hover::after,.component-indicator:hover::before{opacity:1;transition:opacity .2s linear;visibility:visible}.component-indicator span{display:block;height:16px;overflow:hidden;width:16px}.component-indicator span:before{content:'';display:block;font-family:Icons;font-size:16px;height:100%;line-height:16px;width:100%}.component-indicator._on{background:#79a22e}.component-indicator._off{background:#e22626}.component-indicator._off span:before{background:#fff;height:4px;margin:8px auto 20px;width:12px}.component-indicator._info{background:0 0}.component-indicator._info span{width:21px}.component-indicator._info span:before{color:#008bdb;content:'\e648';font-family:Icons;font-size:16px}.component-indicator._tooltip{background:0 0;margin:0 0 8px 5px}.component-indicator._tooltip a{width:21px}.component-indicator._tooltip a:hover{text-decoration:none}.component-indicator._tooltip a:before{color:#514943;content:'\e633';font-family:Icons;font-size:16px}.col-manager-item-name .data-grid-data{padding-left:5px}.col-manager-item-name .ng-hide+.data-grid-data{padding-left:24px}.col-manager-item-name ._hide-dependencies,.col-manager-item-name ._show-dependencies{cursor:pointer;padding-left:24px;position:relative}.col-manager-item-name ._hide-dependencies:before,.col-manager-item-name ._show-dependencies:before{display:block;font-family:Icons;font-size:12px;left:0;position:absolute;top:1px}.col-manager-item-name ._show-dependencies:before{content:'\e62b'}.col-manager-item-name ._hide-dependencies:before{content:'\e628'}.col-manager-item-name ._no-dependencies{padding-left:24px}.product-modules-block{font-size:1.2rem;padding:15px 0 0}.col-manager-item-name .product-modules-block{padding-left:1rem}.product-modules-descriprion,.product-modules-title{font-weight:700;margin:0 0 7px}.product-modules-list{font-size:1.1rem;list-style:none;margin:0}.col-manager-item-name .product-modules-list{margin-left:15px}.col-manager-item-name .product-modules-list li{padding:0 0 0 15px;position:relative}.product-modules-list li{margin:0 0 .5rem}.product-modules-list .component-indicator{height:10px;left:0;position:absolute;top:3px;width:10px}.module-summary{white-space:nowrap}.module-summary-title{font-size:2.1rem;margin-right:1rem}.app-updater .nav{display:block;margin-bottom:3.1rem;margin-top:-2.8rem}.app-updater .nav-bar-outer-actions{margin-top:1rem;padding-right:0}.app-updater .nav-bar-outer-actions .btn-wrap-cancel{margin-right:2.6rem}.main{padding-bottom:2rem;padding-top:3rem}.menu-wrapper .logo-static{pointer-events:none}.header{display:none}.header .logo{float:left;height:4.1rem;width:3.5rem}.header-title{font-size:2.8rem;letter-spacing:.02em;line-height:1.4;margin:2.5rem 0 3.5rem 5rem}.page-title{margin-bottom:1rem}.page-sub-title{font-size:2rem}.accent-box{margin-bottom:2rem}.accent-box .btn-prime{margin-top:1.5rem}.spinner.side{float:left;font-size:2.4rem;margin-left:2rem;margin-top:-5px}.page-landing{margin:7.6% auto 0;max-width:44rem;text-align:center}.page-landing .logo{height:5.6rem;margin-bottom:2rem;width:19.2rem}.page-landing .text-version{margin-bottom:3rem}.page-landing .text-welcome{margin-bottom:6.5rem}.page-landing .text-terms{margin-bottom:2.5rem;text-align:center}.page-landing .btn-submit,.page-license .license-text{margin-bottom:2rem}.page-license .page-license-footer{text-align:right}.readiness-check-item{margin-bottom:4rem;min-height:2.5rem}.readiness-check-item .spinner{float:left;font-size:2.5rem;margin:-.4rem 0 0 1.7rem}.readiness-check-title{font-size:1.4rem;font-weight:700;margin-bottom:.1rem;margin-left:5.7rem}.readiness-check-content{margin-left:5.7rem;margin-right:22rem;position:relative}.readiness-check-content .readiness-check-title{margin-left:0}.readiness-check-content .list{margin-top:-.3rem}.readiness-check-side{left:100%;padding-left:2.4rem;position:absolute;top:0;width:22rem}.readiness-check-side .side-title{margin-bottom:0}.readiness-check-icon{float:left;margin-left:1.7rem;margin-top:.3rem}.extensions-information{margin-bottom:5rem}.extensions-information h3{font-size:1.4rem;margin-bottom:1.3rem}.extensions-information .message{margin-bottom:2.5rem}.extensions-information .message:before{margin-top:0;top:1.8rem}.extensions-information .extensions-container{padding:0 2rem}.extensions-information .list{margin-bottom:1rem}.extensions-information .list select{cursor:pointer}.extensions-information .list select:disabled{background:#ccc;cursor:default}.extensions-information .list .extension-delete{font-size:1.7rem;padding-top:0}.delete-modal-wrap{padding:0 4% 4rem}.delete-modal-wrap h3{font-size:3.4rem;display:inline-block;font-weight:300;margin:0 0 2rem;padding:.9rem 0 0;vertical-align:top}.delete-modal-wrap .actions{padding:3rem 0 0}.page-web-configuration .form-el-insider-wrap{width:auto}.page-web-configuration .form-el-insider{width:15.4rem}.page-web-configuration .form-el-insider-input .form-el-input{width:16.5rem}.customize-your-store .advanced-modules-count,.customize-your-store .advanced-modules-select{padding-left:1.5rem}.customize-your-store .customize-your-store-advanced{min-width:0}.customize-your-store .message-error:before{margin-top:0;top:1.8rem}.customize-your-store .message-error a{color:#333;text-decoration:underline}.customize-your-store .message-error .form-label:before{background:#fff}.customize-your-store .customize-database-clean p{margin-top:2.5rem}.content-install{margin-bottom:2rem}.console{border:1px solid #ccc;font-family:'Courier New',Courier,monospace;font-weight:300;height:20rem;margin:1rem 0 2rem;overflow-y:auto;padding:1.5rem 2rem 2rem;resize:vertical}.console .text-danger{color:#e22626}.console .text-success{color:#090}.console .hidden{display:none}.content-success .btn-prime{margin-top:1.5rem}.jumbo-title{font-size:3.6rem}.jumbo-title .jumbo-icon{font-size:3.8rem;margin-right:.25em;position:relative;top:.15em}.install-database-clean{margin-top:4rem}.install-database-clean .btn{margin-right:1rem}.page-sub-title{margin-bottom:2.1rem;margin-top:3rem}.multiselect-custom{max-width:71.1rem}.content-install{margin-top:3.7rem}.home-page-inner-wrap{margin:0 auto;max-width:91rem}.setup-home-title{margin-bottom:3.9rem;padding-top:1.8rem;text-align:center}.setup-home-item{background-color:#fafafa;border:1px solid #ccc;color:#333;display:block;margin-bottom:2rem;margin-left:1.3rem;margin-right:1.3rem;min-height:30rem;padding:2rem;text-align:center}.setup-home-item:hover{border-color:#8c8c8c;color:#333;text-decoration:none;transition:border-color .1s linear}.setup-home-item:active{-ms-transform:scale(0.99);transform:scale(0.99)}.setup-home-item:before{display:block;font-size:7rem;margin-bottom:3.3rem;margin-top:4rem}.setup-home-item-component:before,.setup-home-item-extension:before{content:'\e612'}.setup-home-item-module:before{content:'\e647'}.setup-home-item-upgrade:before{content:'\e614'}.setup-home-item-configuration:before{content:'\e610'}.setup-home-item-title{display:block;font-size:1.8rem;letter-spacing:.025em;margin-bottom:1rem}.setup-home-item-description{display:block}.extension-manager-wrap{border:1px solid #bbb;margin:0 0 4rem}.extension-manager-account{font-size:2.1rem;display:inline-block;font-weight:400}.extension-manager-title{font-size:3.2rem;background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;color:#41362f;font-weight:600;line-height:1.2;padding:2rem}.extension-manager-content{padding:2.5rem 2rem 2rem}.extension-manager-items{list-style:none;margin:0;text-align:center}.extension-manager-items .btn{border:1px solid #adadad;display:block;margin:1rem auto 0}.extension-manager-items .item-title{font-size:2.1rem;display:inline-block;text-align:left}.extension-manager-items .item-number{font-size:4.1rem;display:inline-block;line-height:.8;margin:0 5px 1.5rem 0;vertical-align:top}.extension-manager-items .item-date{font-size:2.6rem;margin-top:1px}.extension-manager-items .item-date-title{font-size:1.5rem}.extension-manager-items .item-install{margin:0 0 2rem}.sync-login-wrap{padding:0 10% 4rem}.sync-login-wrap .legend{font-size:2.6rem;color:#eb5202;float:left;font-weight:300;line-height:1.2;margin:-1rem 0 2.5rem;position:static;width:100%}.sync-login-wrap .legend._hidden{display:none}.sync-login-wrap .login-header{font-size:3.4rem;font-weight:300;margin:0 0 2rem}.sync-login-wrap .login-header span{display:inline-block;padding:.9rem 0 0;vertical-align:top}.sync-login-wrap h4{font-size:1.4rem;margin:0 0 2rem}.sync-login-wrap .sync-login-steps{margin:0 0 2rem 1.5rem}.sync-login-wrap .sync-login-steps li{padding:0 0 0 1rem}.sync-login-wrap .form-row .form-label{display:inline-block}.sync-login-wrap .form-row .form-label.required{padding-left:1.5rem}.sync-login-wrap .form-row .form-label.required:after{left:0;position:absolute;right:auto}.sync-login-wrap .form-row{max-width:28rem}.sync-login-wrap .form-actions{display:table;margin-top:-1.3rem}.sync-login-wrap .form-actions .links{display:table-header-group}.sync-login-wrap .form-actions .actions{padding:3rem 0 0}@media all and (max-width:1047px){.admin__menu .submenu li{min-width:19.8rem}.nav{padding-bottom:5.38rem;padding-left:1.5rem;text-align:center}.nav-bar{display:inline-block;float:none;margin-right:0;vertical-align:top}.nav .btn-group,.nav-bar-outer-actions{display:inline-block;float:none;margin-top:-8.48rem;text-align:center;vertical-align:top;width:100%}.nav-bar-outer-actions{padding-right:0}.nav-bar-outer-actions .outer-actions-inner-wrap{display:inline-block}.app-updater .nav{padding-bottom:1.7rem}.app-updater .nav-bar-outer-actions{margin-top:2rem}}@media all and (min-width:768px){.page-layout-admin-2columns-left .page-columns{margin-left:-30px}.page-layout-admin-2columns-left .page-columns:after{clear:both;content:'';display:table}.page-layout-admin-2columns-left .page-columns .main-col{width:calc((100%) * .75 - 30px);float:right}.page-layout-admin-2columns-left .page-columns .side-col{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9{float:left}.col-m-12{width:100%}.col-m-11{width:91.66666667%}.col-m-10{width:83.33333333%}.col-m-9{width:75%}.col-m-8{width:66.66666667%}.col-m-7{width:58.33333333%}.col-m-6{width:50%}.col-m-5{width:41.66666667%}.col-m-4{width:33.33333333%}.col-m-3{width:25%}.col-m-2{width:16.66666667%}.col-m-1{width:8.33333333%}.col-m-pull-12{right:100%}.col-m-pull-11{right:91.66666667%}.col-m-pull-10{right:83.33333333%}.col-m-pull-9{right:75%}.col-m-pull-8{right:66.66666667%}.col-m-pull-7{right:58.33333333%}.col-m-pull-6{right:50%}.col-m-pull-5{right:41.66666667%}.col-m-pull-4{right:33.33333333%}.col-m-pull-3{right:25%}.col-m-pull-2{right:16.66666667%}.col-m-pull-1{right:8.33333333%}.col-m-pull-0{right:auto}.col-m-push-12{left:100%}.col-m-push-11{left:91.66666667%}.col-m-push-10{left:83.33333333%}.col-m-push-9{left:75%}.col-m-push-8{left:66.66666667%}.col-m-push-7{left:58.33333333%}.col-m-push-6{left:50%}.col-m-push-5{left:41.66666667%}.col-m-push-4{left:33.33333333%}.col-m-push-3{left:25%}.col-m-push-2{left:16.66666667%}.col-m-push-1{left:8.33333333%}.col-m-push-0{left:auto}.col-m-offset-12{margin-left:100%}.col-m-offset-11{margin-left:91.66666667%}.col-m-offset-10{margin-left:83.33333333%}.col-m-offset-9{margin-left:75%}.col-m-offset-8{margin-left:66.66666667%}.col-m-offset-7{margin-left:58.33333333%}.col-m-offset-6{margin-left:50%}.col-m-offset-5{margin-left:41.66666667%}.col-m-offset-4{margin-left:33.33333333%}.col-m-offset-3{margin-left:25%}.col-m-offset-2{margin-left:16.66666667%}.col-m-offset-1{margin-left:8.33333333%}.col-m-offset-0{margin-left:0}.page-columns{margin-left:-30px}.page-columns:after{clear:both;content:'';display:table}.page-columns .page-inner-content{width:calc((100%) * .75 - 30px);float:right}.page-columns .page-inner-sidebar{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}}@media all and (min-width:1048px){.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9{float:left}.col-l-12{width:100%}.col-l-11{width:91.66666667%}.col-l-10{width:83.33333333%}.col-l-9{width:75%}.col-l-8{width:66.66666667%}.col-l-7{width:58.33333333%}.col-l-6{width:50%}.col-l-5{width:41.66666667%}.col-l-4{width:33.33333333%}.col-l-3{width:25%}.col-l-2{width:16.66666667%}.col-l-1{width:8.33333333%}.col-l-pull-12{right:100%}.col-l-pull-11{right:91.66666667%}.col-l-pull-10{right:83.33333333%}.col-l-pull-9{right:75%}.col-l-pull-8{right:66.66666667%}.col-l-pull-7{right:58.33333333%}.col-l-pull-6{right:50%}.col-l-pull-5{right:41.66666667%}.col-l-pull-4{right:33.33333333%}.col-l-pull-3{right:25%}.col-l-pull-2{right:16.66666667%}.col-l-pull-1{right:8.33333333%}.col-l-pull-0{right:auto}.col-l-push-12{left:100%}.col-l-push-11{left:91.66666667%}.col-l-push-10{left:83.33333333%}.col-l-push-9{left:75%}.col-l-push-8{left:66.66666667%}.col-l-push-7{left:58.33333333%}.col-l-push-6{left:50%}.col-l-push-5{left:41.66666667%}.col-l-push-4{left:33.33333333%}.col-l-push-3{left:25%}.col-l-push-2{left:16.66666667%}.col-l-push-1{left:8.33333333%}.col-l-push-0{left:auto}.col-l-offset-12{margin-left:100%}.col-l-offset-11{margin-left:91.66666667%}.col-l-offset-10{margin-left:83.33333333%}.col-l-offset-9{margin-left:75%}.col-l-offset-8{margin-left:66.66666667%}.col-l-offset-7{margin-left:58.33333333%}.col-l-offset-6{margin-left:50%}.col-l-offset-5{margin-left:41.66666667%}.col-l-offset-4{margin-left:33.33333333%}.col-l-offset-3{margin-left:25%}.col-l-offset-2{margin-left:16.66666667%}.col-l-offset-1{margin-left:8.33333333%}.col-l-offset-0{margin-left:0}}@media all and (min-width:1440px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-pull-12{right:100%}.col-xl-pull-11{right:91.66666667%}.col-xl-pull-10{right:83.33333333%}.col-xl-pull-9{right:75%}.col-xl-pull-8{right:66.66666667%}.col-xl-pull-7{right:58.33333333%}.col-xl-pull-6{right:50%}.col-xl-pull-5{right:41.66666667%}.col-xl-pull-4{right:33.33333333%}.col-xl-pull-3{right:25%}.col-xl-pull-2{right:16.66666667%}.col-xl-pull-1{right:8.33333333%}.col-xl-pull-0{right:auto}.col-xl-push-12{left:100%}.col-xl-push-11{left:91.66666667%}.col-xl-push-10{left:83.33333333%}.col-xl-push-9{left:75%}.col-xl-push-8{left:66.66666667%}.col-xl-push-7{left:58.33333333%}.col-xl-push-6{left:50%}.col-xl-push-5{left:41.66666667%}.col-xl-push-4{left:33.33333333%}.col-xl-push-3{left:25%}.col-xl-push-2{left:16.66666667%}.col-xl-push-1{left:8.33333333%}.col-xl-push-0{left:auto}.col-xl-offset-12{margin-left:100%}.col-xl-offset-11{margin-left:91.66666667%}.col-xl-offset-10{margin-left:83.33333333%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-8{margin-left:66.66666667%}.col-xl-offset-7{margin-left:58.33333333%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-5{margin-left:41.66666667%}.col-xl-offset-4{margin-left:33.33333333%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-2{margin-left:16.66666667%}.col-xl-offset-1{margin-left:8.33333333%}.col-xl-offset-0{margin-left:0}}@media all and (max-width:767px){.abs-clearer-mobile:after,.nav-bar:after{clear:both;content:'';display:table}.list-definition>dt{float:none}.list-definition>dd{margin-left:0}.form-row .form-label{text-align:left}.form-row .form-label.required:after{position:static}.nav{padding-bottom:0;padding-left:0;padding-right:0}.nav-bar-outer-actions{margin-top:0}.nav-bar{display:block;margin-bottom:0;margin-left:auto;margin-right:auto;width:30.9rem}.nav-bar:before{display:none}.nav-bar>li{float:left;min-height:9rem}.nav-bar>li:after{display:none}.nav-bar>li:nth-child(4n){clear:both}.nav-bar a{line-height:1.4}.tooltip{display:none!important}.readiness-check-content{margin-right:2rem}.readiness-check-side{padding:2rem 0;position:static}.form-el-insider,.form-el-insider-wrap,.page-web-configuration .form-el-insider-input,.page-web-configuration .form-el-insider-input .form-el-input{display:block;width:100%}}@media all and (max-width:479px){.nav-bar{width:23.175rem}.nav-bar>li{width:7.725rem}.nav .btn-group .btn-wrap-try-again,.nav-bar-outer-actions .btn-wrap-try-again{clear:both;display:block;float:none;margin-left:auto;margin-right:auto;margin-top:1rem;padding-top:1rem}} \ No newline at end of file +.abs-action-delete,.abs-icon,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.validation-symbol:after{color:#e22626;content:'*';font-weight:400;margin-left:3px}.abs-modal-overlay,.modals-overlay{background:rgba(0,0,0,.35);bottom:0;left:0;position:fixed;right:0;top:0}.abs-action-delete>span,.abs-visually-hidden,.action-multicheck-wrap .action-multicheck-toggle>span,.admin__actions-switch-checkbox,.admin__control-fields .admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label)>.admin__field-label,.admin__field-tooltip .admin__field-tooltip-action span,.customize-your-store .customize-your-store-default .legend,.extensions-information .list .extension-delete>span,.form-el-checkbox,.form-el-radio,.selectmenu .action-delete>span,.selectmenu .action-edit>span,.selectmenu .action-save>span,.selectmenu-toggle span,.tooltip .help a span,.tooltip .help span span,[class*=admin__control-grouped]>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.abs-visually-hidden-reset,.admin__field-group-columns>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label[class]{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.abs-clearfix:after,.abs-clearfix:before,.action-multicheck-wrap:after,.action-multicheck-wrap:before,.actions-split:after,.actions-split:before,.admin__control-table-pagination:after,.admin__control-table-pagination:before,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:before,.admin__data-grid-filters-footer:after,.admin__data-grid-filters-footer:before,.admin__data-grid-filters:after,.admin__data-grid-filters:before,.admin__data-grid-header-row:after,.admin__data-grid-header-row:before,.admin__field-complex:after,.admin__field-complex:before,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .magento-message .insert-title-inner:before,.modal-slide .main-col .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:before,.page-actions._fixed:after,.page-actions._fixed:before,.page-content:after,.page-content:before,.page-header-actions:after,.page-header-actions:before,.page-main-actions:not(._hidden):after,.page-main-actions:not(._hidden):before{content:'';display:table}.abs-clearfix:after,.action-multicheck-wrap:after,.actions-split:after,.admin__control-table-pagination:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-filters-footer:after,.admin__data-grid-filters:after,.admin__data-grid-header-row:after,.admin__field-complex:after,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:after,.page-actions._fixed:after,.page-content:after,.page-header-actions:after,.page-main-actions:not(._hidden):after{clear:both}.abs-list-reset-styles{margin:0;padding:0;list-style:none}.abs-draggable-handle,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle,.admin__control-table .draggable-handle,.data-grid .data-grid-draggable-row-cell .draggable-handle{cursor:-webkit-grab;cursor:move;font-size:0;margin-top:-4px;padding:0 1rem 0 0;vertical-align:middle;display:inline-block;text-decoration:none}.abs-draggable-handle:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:before,.admin__control-table .draggable-handle:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:before{-webkit-font-smoothing:antialiased;font-size:1.8rem;line-height:inherit;color:#9e9e9e;content:'\e617';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.abs-draggable-handle:hover:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:hover:before,.admin__control-table .draggable-handle:hover:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:hover:before{color:#858585}.abs-config-scope-label,.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]:before{bottom:-1.3rem;color:gray;content:attr(data-config-scope);font-size:1.1rem;font-weight:400;min-width:15rem;position:absolute;right:0;text-transform:lowercase}.abs-word-wrap,.admin__field:not(.admin__field-option)>.admin__field-label{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;box-sizing:border-box}*,:after,:before{box-sizing:inherit}:focus{box-shadow:none;outline:0}._keyfocus :focus{box-shadow:0 0 0 1px #008bdb}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}embed,img,object,video{max-width:100%}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/light/opensans-300.eot);src:url(../fonts/opensans/light/opensans-300.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/light/opensans-300.woff2) format('woff2'),url(../fonts/opensans/light/opensans-300.woff) format('woff'),url(../fonts/opensans/light/opensans-300.ttf) format('truetype'),url('../fonts/opensans/light/opensans-300.svg#Open Sans') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/regular/opensans-400.eot);src:url(../fonts/opensans/regular/opensans-400.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/regular/opensans-400.woff2) format('woff2'),url(../fonts/opensans/regular/opensans-400.woff) format('woff'),url(../fonts/opensans/regular/opensans-400.ttf) format('truetype'),url('../fonts/opensans/regular/opensans-400.svg#Open Sans') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/semibold/opensans-600.eot);src:url(../fonts/opensans/semibold/opensans-600.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/semibold/opensans-600.woff2) format('woff2'),url(../fonts/opensans/semibold/opensans-600.woff) format('woff'),url(../fonts/opensans/semibold/opensans-600.ttf) format('truetype'),url('../fonts/opensans/semibold/opensans-600.svg#Open Sans') format('svg');font-weight:600;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/bold/opensans-700.eot);src:url(../fonts/opensans/bold/opensans-700.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/bold/opensans-700.woff2) format('woff2'),url(../fonts/opensans/bold/opensans-700.woff) format('woff'),url(../fonts/opensans/bold/opensans-700.ttf) format('truetype'),url('../fonts/opensans/bold/opensans-700.svg#Open Sans') format('svg');font-weight:700;font-style:normal}html{font-size:62.5%}body{color:#333;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.36;font-size:1.4rem}h1{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2.8rem}h2{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2rem}h3{margin:0 0 2rem;color:#41362f;font-weight:600;line-height:1.2;font-size:1.7rem}h4,h5,h6{font-weight:600;margin-top:0}p{margin:0 0 1em}small{font-size:1.2rem}a{color:#008bdb;text-decoration:none}a:hover{color:#0fa7ff;text-decoration:underline}dl,ol,ul{padding-left:0}nav ol,nav ul{list-style:none;margin:0;padding:0}html{height:100%}body{background-color:#fff;min-height:100%;min-width:102.4rem}.page-wrapper{background-color:#fff;display:inline-block;margin-left:-4px;vertical-align:top;width:calc(100% - 8.8rem)}.page-content{padding-bottom:3rem;padding-left:3rem;padding-right:3rem}.notices-wrapper{margin:0 3rem}.notices-wrapper .messages{margin-bottom:0}.row{margin-left:0;margin-right:0}.row:after{clear:both;content:'';display:table}.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9,.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{min-height:1px;padding-left:0;padding-right:0;position:relative}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}.row-gutter{margin-left:-1.5rem;margin-right:-1.5rem}.row-gutter>[class*=col-]{padding-left:1.5rem;padding-right:1.5rem}.abs-clearer:after,.extension-manager-content:after,.extension-manager-title:after,.form-row:after,.header:after,.nav:after,body:after{clear:both;content:'';display:table}.ng-cloak{display:none!important}.hide.hide{display:none}.show.show{display:block}.text-center{text-align:center}.text-right{text-align:right}@font-face{font-family:Icons;src:url(../fonts/icons/icons.eot);src:url(../fonts/icons/icons.eot?#iefix) format('embedded-opentype'),url(../fonts/icons/icons.woff2) format('woff2'),url(../fonts/icons/icons.woff) format('woff'),url(../fonts/icons/icons.ttf) format('truetype'),url(../fonts/icons/icons.svg#Icons) format('svg');font-weight:400;font-style:normal}[class*=icon-]{display:inline-block;line-height:1}.icon-failed:before,.icon-success:before,[class*=icon-]:after{font-family:Icons}.icon-success{color:#79a22e}.icon-success:before{content:'\e62d'}.icon-failed{color:#e22626}.icon-failed:before{content:'\e632'}.icon-success-thick:after{content:'\e62d'}.icon-collapse:after{content:'\e615'}.icon-failed-thick:after{content:'\e632'}.icon-expand:after{content:'\e616'}.icon-warning:after{content:'\e623'}.icon-failed-round,.icon-success-round{border-radius:100%;color:#fff;font-size:2.5rem;height:1em;position:relative;text-align:center;width:1em}.icon-failed-round:after,.icon-success-round:after{bottom:0;font-size:.5em;left:0;position:absolute;right:0;top:.45em}.icon-success-round{background-color:#79a22e}.icon-success-round:after{content:'\e62d'}.icon-failed-round{background-color:#e22626}.icon-failed-round:after{content:'\e632'}dl,ol,ul{margin-top:0}.list{padding-left:0}.list>li{display:block;margin-bottom:.75em;position:relative}.list>li>.icon-failed,.list>li>.icon-success{font-size:1.6em;left:-.1em;position:absolute;top:0}.list>li>.icon-success{color:#79a22e}.list>li>.icon-failed{color:#e22626}.list-item-failed,.list-item-icon,.list-item-success,.list-item-warning{padding-left:3.5rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{left:-.1em;position:absolute}.list-item-success:before{color:#79a22e}.list-item-failed:before{color:#e22626}.list-item-warning:before{color:#ef672f}.list-definition{margin:0 0 3rem;padding:0}.list-definition>dt{clear:left;float:left}.list-definition>dd{margin-bottom:1em;margin-left:20rem}.btn-wrap{margin:0 auto}.btn-wrap .btn{width:100%}.btn{background:#e3e3e3;border:none;color:#514943;display:inline-block;font-size:1.6rem;font-weight:600;padding:.45em .9em;text-align:center}.btn:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.btn:active{background-color:#d6d6d6}.btn.disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.ie9 .btn.disabled,.ie9 .btn[disabled]{background-color:#f0f0f0;opacity:1;text-shadow:none}.btn-large{padding:.75em 1.25em}.btn-medium{font-size:1.4rem;padding:.5em 1.5em .6em}.btn-link{background-color:transparent;border:none;color:#008bdb;font-family:1.6rem;font-size:1.5rem}.btn-link:active,.btn-link:focus,.btn-link:hover{background-color:transparent;color:#0fa7ff}.btn-prime{background-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.btn-prime:focus,.btn-prime:hover{background-color:#f65405;background-repeat:repeat-x;background-image:linear-gradient(to right,#e04f00 0,#f65405 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e04f00', endColorstr='#f65405', GradientType=1);color:#fff}.btn-prime:active{background-color:#e04f00;background-repeat:repeat-x;background-image:linear-gradient(to right,#f65405 0,#e04f00 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f65405', endColorstr='#e04f00', GradientType=1);color:#fff}.ie9 .btn-prime.disabled,.ie9 .btn-prime[disabled]{background-color:#fd6e23}.ie9 .btn-prime.disabled:active,.ie9 .btn-prime.disabled:hover,.ie9 .btn-prime[disabled]:active,.ie9 .btn-prime[disabled]:hover{background-color:#fd6e23;-webkit-filter:none;filter:none}.btn-secondary{background-color:#514943;color:#fff}.btn-secondary:hover{background-color:#5f564f;color:#fff}.btn-secondary:active,.btn-secondary:focus{background-color:#574e48;color:#fff}.ie9 .btn-secondary.disabled,.ie9 .btn-secondary[disabled]{background-color:#514943}.ie9 .btn-secondary.disabled:active,.ie9 .btn-secondary[disabled]:active{background-color:#514943;-webkit-filter:none;filter:none}[class*=btn-wrap-triangle]{overflow:hidden;position:relative}[class*=btn-wrap-triangle] .btn:after{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.btn-wrap-triangle-right{display:inline-block;padding-right:1.74rem;position:relative}.btn-wrap-triangle-right .btn{text-indent:.92rem}.btn-wrap-triangle-right .btn:after{border-color:transparent transparent transparent #e3e3e3;border-width:1.84rem 0 1.84rem 1.84rem;left:100%;margin-left:-1.74rem}.btn-wrap-triangle-right .btn:focus:after,.btn-wrap-triangle-right .btn:hover:after{border-left-color:#dbdbdb}.btn-wrap-triangle-right .btn:active:after{border-left-color:#d6d6d6}.btn-wrap-triangle-right .btn:not(.disabled):active,.btn-wrap-triangle-right .btn:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn.disabled:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:after{border-color:transparent transparent transparent #f0f0f0}.ie9 .btn-wrap-triangle-right .btn.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn.disabled:focus:after,.ie9 .btn-wrap-triangle-right .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:focus:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:hover:after{border-left-color:#f0f0f0}.btn-wrap-triangle-right .btn-prime:after{border-color:transparent transparent transparent #eb5202}.btn-wrap-triangle-right .btn-prime:focus:after,.btn-wrap-triangle-right .btn-prime:hover:after{border-left-color:#f65405}.btn-wrap-triangle-right .btn-prime:active:after{border-left-color:#e04f00}.btn-wrap-triangle-right .btn-prime:not(.disabled):active,.btn-wrap-triangle-right .btn-prime:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:after{border-color:transparent transparent transparent #fd6e23}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:hover:after{border-left-color:#fd6e23}.btn-wrap-triangle-left{display:inline-block;padding-left:1.74rem}.btn-wrap-triangle-left .btn{text-indent:-.92rem}.btn-wrap-triangle-left .btn:after{border-color:transparent #e3e3e3 transparent transparent;border-width:1.84rem 1.84rem 1.84rem 0;margin-right:-1.74rem;right:100%}.btn-wrap-triangle-left .btn:focus:after,.btn-wrap-triangle-left .btn:hover:after{border-right-color:#dbdbdb}.btn-wrap-triangle-left .btn:active:after{border-right-color:#d6d6d6}.btn-wrap-triangle-left .btn:not(.disabled):active,.btn-wrap-triangle-left .btn:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn.disabled:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:after{border-color:transparent #f0f0f0 transparent transparent}.ie9 .btn-wrap-triangle-left .btn.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:hover:after{border-right-color:#f0f0f0}.btn-wrap-triangle-left .btn-prime:after{border-color:transparent #eb5202 transparent transparent}.btn-wrap-triangle-left .btn-prime:focus:after,.btn-wrap-triangle-left .btn-prime:hover:after{border-right-color:#e04f00}.btn-wrap-triangle-left .btn-prime:active:after{border-right-color:#f65405}.btn-wrap-triangle-left .btn-prime:not(.disabled):active,.btn-wrap-triangle-left .btn-prime:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:after{border-color:transparent #fd6e23 transparent transparent}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:hover:after{border-right-color:#fd6e23}.btn-expand{background-color:transparent;border:none;color:#303030;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700;padding:0;position:relative}.btn-expand.expanded:after{border-color:transparent transparent #303030;border-width:0 .285em .36em}.btn-expand.expanded:hover:after{border-color:transparent transparent #3d3d3d}.btn-expand:hover{background-color:transparent;border:none;color:#3d3d3d}.btn-expand:hover:after{border-color:#3d3d3d transparent transparent}.btn-expand:after{border-color:#303030 transparent transparent;border-style:solid;border-width:.36em .285em 0;content:'';height:0;left:100%;margin-left:.5em;margin-top:-.18em;position:absolute;top:50%;width:0}[class*=col-] .form-el-input,[class*=col-] .form-el-select{width:100%}.form-fieldset{border:none;margin:0 0 1em;padding:0}.form-row{margin-bottom:2.2rem}.form-row .form-row{margin-bottom:.4rem}.form-row .form-label{display:block;font-weight:600;padding:.6rem 2.1em 0 0;text-align:right}.form-row .form-label.required{position:relative}.form-row .form-label.required:after{color:#eb5202;content:'*';font-size:1.15em;position:absolute;right:.7em;top:.5em}.form-row .form-el-checkbox+.form-label:before,.form-row .form-el-radio+.form-label:before{top:.7rem}.form-row .form-el-checkbox+.form-label:after,.form-row .form-el-radio+.form-label:after{top:1.1rem}.form-row.form-row-text{padding-top:.6rem}.form-row.form-row-text .action-sign-out{font-size:1.2rem;margin-left:1rem}.form-note{font-size:1.2rem;font-weight:600;margin-top:1rem}.form-el-dummy{display:none}.fieldset{border:0;margin:0;min-width:0;padding:0}input:not([disabled]):focus,textarea:not([disabled]):focus{box-shadow:none}.form-el-input{border:1px solid #adadad;color:#303030;padding:.35em .55em .5em}.form-el-input:hover{border-color:#949494}.form-el-input:focus{border-color:#008bdb}.form-el-input:required{box-shadow:none}.form-label{margin-bottom:.5em}[class*=form-label][for]{cursor:pointer}.form-el-insider-wrap{display:table;width:100%}.form-el-insider-input{display:table-cell;width:100%}.form-el-insider{border-radius:2px;display:table-cell;padding:.43em .55em .5em 0;vertical-align:top}.form-legend,.form-legend-expand,.form-legend-light{display:block;margin:0}.form-legend,.form-legend-expand{font-size:1.25em;font-weight:600;margin-bottom:2.5em;padding-top:1.5em}.form-legend{border-top:1px solid #ccc;width:100%}.form-legend-light{font-size:1em;margin-bottom:1.5em}.form-legend-expand{cursor:pointer;transition:opacity .2s linear}.form-legend-expand:hover{opacity:.85}.form-legend-expand.expanded:after{content:'\e615'}.form-legend-expand:after{content:'\e616';font-family:Icons;font-size:1.15em;font-weight:400;margin-left:.5em;vertical-align:sub}.form-el-checkbox.disabled+.form-label,.form-el-checkbox.disabled+.form-label:before,.form-el-checkbox[disabled]+.form-label,.form-el-checkbox[disabled]+.form-label:before,.form-el-radio.disabled+.form-label,.form-el-radio.disabled+.form-label:before,.form-el-radio[disabled]+.form-label,.form-el-radio[disabled]+.form-label:before{cursor:default;opacity:.5;pointer-events:none}.form-el-checkbox:not(.disabled)+.form-label:hover:before,.form-el-checkbox:not([disabled])+.form-label:hover:before,.form-el-radio:not(.disabled)+.form-label:hover:before,.form-el-radio:not([disabled])+.form-label:hover:before{border-color:#514943}.form-el-checkbox+.form-label,.form-el-radio+.form-label{font-weight:400;padding-left:2em;padding-right:0;position:relative;text-align:left;transition:border-color .1s linear}.form-el-checkbox+.form-label:before,.form-el-radio+.form-label:before{border:1px solid;content:'';left:0;position:absolute;top:.1rem;transition:border-color .1s linear}.form-el-checkbox+.form-label:before{background-color:#fff;border-color:#adadad;border-radius:2px;font-size:1.2rem;height:1.6rem;line-height:1.2;width:1.6rem}.form-el-checkbox:checked+.form-label::before{content:'\e62d';font-family:Icons}.form-el-radio+.form-label:before{background-color:#fff;border:1px solid #adadad;border-radius:100%;height:1.8rem;width:1.8rem}.form-el-radio+.form-label:after{background:0 0;border:.5rem solid transparent;border-radius:100%;content:'';height:0;left:.4rem;position:absolute;top:.5rem;transition:background .3s linear;width:0}.form-el-radio:checked+.form-label{cursor:default}.form-el-radio:checked+.form-label:after{border-color:#514943}.form-select-label{border:1px solid #adadad;color:#303030;cursor:pointer;display:block;overflow:hidden;position:relative;z-index:0}.form-select-label:hover,.form-select-label:hover:after{border-color:#949494}.form-select-label:active,.form-select-label:active:after,.form-select-label:focus,.form-select-label:focus:after{border-color:#008bdb}.form-select-label:after{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:2.36em;z-index:-2}.ie9 .form-select-label:after{display:none}.form-select-label:before{border-color:#303030 transparent transparent;border-style:solid;border-width:5px 4px 0;content:'';height:0;margin-right:-4px;margin-top:-2.5px;position:absolute;right:1.18em;top:50%;width:0;z-index:-1}.ie9 .form-select-label:before{display:none}.form-select-label .form-el-select{background:0 0;border:none;border-radius:0;content:'';display:block;margin:0;padding:.35em calc(2.36em + 10%) .5em .55em;width:110%}.ie9 .form-select-label .form-el-select{padding-right:.55em;width:100%}.form-select-label .form-el-select::-ms-expand{display:none}.form-el-select{background:#fff;border:1px solid #adadad;border-radius:2px;color:#303030;display:block;padding:.35em .55em}.multiselect-custom{border:1px solid #adadad;height:45.2rem;margin:0 0 1.5rem;overflow:auto;position:relative}.multiselect-custom ul{margin:0;padding:0;list-style:none;min-width:29rem}.multiselect-custom .item{padding:1rem 1.4rem}.multiselect-custom .selected{background-color:#e0f6fe}.multiselect-custom .form-label{margin-bottom:0}[class*=form-el-].invalid{border-color:#e22626}[class*=form-el-].invalid+.error-container{display:block}.error-container{background-color:#fffbbb;border:1px solid #ee7d7d;color:#514943;display:none;font-size:1.19rem;margin-top:.2rem;padding:.8rem 1rem .9rem}.check-result-message{margin-left:.5em;min-height:3.68rem;-ms-align-items:center;-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.check-result-text{margin-left:.5em}body:not([class]){min-width:0}.container{display:block;margin:0 auto 4rem;max-width:100rem;padding:0}.abs-action-delete,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.text-stretch{margin-bottom:1.5em}.page-title-jumbo{font-size:4rem;font-weight:300;letter-spacing:-.05em;margin-bottom:2.9rem}.page-title-jumbo-success:before{color:#79a22e;content:'\e62d';font-size:3.9rem;margin-left:-.3rem;margin-right:2.4rem}.list{margin-bottom:3rem}.list-dot .list-item{display:list-item;list-style-position:inside;margin-bottom:1.2rem}.list-title{color:#333;font-size:1.4rem;font-weight:700;letter-spacing:.025em;margin-bottom:1.2rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{font-family:Icons;font-size:1.6rem;top:0}.list-item-success:before{content:'\e62d';font-size:1.6rem}.list-item-failed:before{content:'\e632';font-size:1.4rem;left:.1rem;top:.2rem}.list-item-warning:before{content:'\e623';font-size:1.3rem;left:.2rem}.form-wrap{margin-bottom:3.6rem;padding-top:2.1rem}.form-el-label-horizontal{display:inline-block;font-size:1.3rem;font-weight:600;letter-spacing:.025em;margin-bottom:.4rem;margin-left:.4rem}.app-updater{min-width:768px}body._has-modal{height:100%;overflow:hidden;width:100%}.modals-overlay{z-index:899}.modal-popup,.modal-slide{bottom:0;min-width:0;position:fixed;right:0;top:0;visibility:hidden}.modal-popup._show,.modal-slide._show{visibility:visible}.modal-popup._show .modal-inner-wrap,.modal-slide._show .modal-inner-wrap{-ms-transform:translate(0,0);transform:translate(0,0)}.modal-popup .modal-inner-wrap,.modal-slide .modal-inner-wrap{background-color:#fff;box-shadow:0 0 12px 2px rgba(0,0,0,.35);opacity:1;pointer-events:auto}.modal-slide{left:14.8rem;z-index:900}.modal-slide._show .modal-inner-wrap{-ms-transform:translateX(0);transform:translateX(0)}.modal-slide .modal-inner-wrap{height:100%;overflow-y:auto;position:static;-ms-transform:translateX(100%);transform:translateX(100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;width:auto}.modal-slide._inner-scroll .modal-inner-wrap{overflow-y:visible;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.modal-slide._inner-scroll .modal-footer,.modal-slide._inner-scroll .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-slide._inner-scroll .modal-content{overflow-y:auto}.modal-slide._inner-scroll .modal-footer{margin-top:auto}.modal-slide .modal-content,.modal-slide .modal-footer,.modal-slide .modal-header{padding:0 2.6rem 2.6rem}.modal-slide .modal-header{padding-bottom:2.1rem;padding-top:2.1rem}.modal-popup{z-index:900;left:0;overflow-y:auto}.modal-popup._show .modal-inner-wrap{-ms-transform:translateY(0);transform:translateY(0)}.modal-popup .modal-inner-wrap{margin:5rem auto;width:75%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;box-sizing:border-box;height:auto;left:0;position:absolute;right:0;-ms-transform:translateY(-200%);transform:translateY(-200%);transition-duration:.2s;transition-property:transform,visibility;transition-timing-function:ease}.modal-popup._inner-scroll{overflow-y:visible}.ie10 .modal-popup._inner-scroll,.ie9 .modal-popup._inner-scroll{overflow-y:auto}.modal-popup._inner-scroll .modal-inner-wrap{max-height:90%}.ie10 .modal-popup._inner-scroll .modal-inner-wrap,.ie9 .modal-popup._inner-scroll .modal-inner-wrap{max-height:none}.modal-popup._inner-scroll .modal-content{overflow-y:auto}.modal-popup .modal-content,.modal-popup .modal-footer,.modal-popup .modal-header{padding-left:3rem;padding-right:3rem}.modal-popup .modal-footer,.modal-popup .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-popup .modal-header{padding-bottom:1.2rem;padding-top:3rem}.modal-popup .modal-footer{margin-top:auto;padding-bottom:3rem}.modal-popup .modal-footer-actions{text-align:right}.admin__action-dropdown-wrap{display:inline-block;position:relative}.admin__action-dropdown-wrap .admin__action-dropdown-text:after{left:-6px;right:0}.admin__action-dropdown-wrap .admin__action-dropdown-menu{left:auto;right:0}.admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__action-dropdown-wrap.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin__action-dropdown-wrap._active .admin__action-dropdown-text:after,.admin__action-dropdown-wrap.active .admin__action-dropdown-text:after{background-color:#fff;content:'';height:6px;position:absolute;top:100%}.admin__action-dropdown-wrap._active .admin__action-dropdown-menu,.admin__action-dropdown-wrap.active .admin__action-dropdown-menu{display:block}.admin__action-dropdown-wrap._disabled .admin__action-dropdown{cursor:default}.admin__action-dropdown-wrap._disabled:hover .admin__action-dropdown{color:#333}.admin__action-dropdown{background-color:#fff;border:1px solid transparent;border-bottom:none;border-radius:0;box-shadow:none;color:#333;display:inline-block;font-size:1.3rem;font-weight:400;letter-spacing:-.025em;padding:.7rem 3.3rem .8rem 1.5rem;position:relative;vertical-align:baseline;z-index:2}.admin__action-dropdown._active:after,.admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .admin__action-dropdown:after,.active .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin__action-dropdown:focus,.admin__action-dropdown:hover{background-color:#fff;color:#000;text-decoration:none}.admin__action-dropdown:after{right:1.5rem}.admin__action-dropdown:before{margin-right:1rem}.admin__action-dropdown-menu{background-color:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;line-height:1.36;margin-top:-1px;min-width:120%;padding:.5rem 1rem;position:absolute;top:100%;transition:all .15s ease;z-index:1}.admin__action-dropdown-menu>li{display:block}.admin__action-dropdown-menu>li>a{color:#333;display:block;text-decoration:none;padding:.6rem .5rem}.selectmenu{display:inline-block;position:relative;text-align:left;z-index:1}.selectmenu._active{border-color:#007bdb;z-index:500}.selectmenu .action-delete,.selectmenu .action-edit,.selectmenu .action-save{background-color:transparent;border-color:transparent;box-shadow:none;padding:0 1rem}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover,.selectmenu .action-save:hover{background-color:transparent;border-color:transparent;box-shadow:none}.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before{content:'\e630'}.selectmenu .action-delete,.selectmenu .action-edit{border:0 solid #fff;border-left-width:1px;bottom:0;position:absolute;right:0;top:0;z-index:1}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover{border:0 solid #fff;border-left-width:1px}.selectmenu .action-save:before{content:'\e625'}.selectmenu .action-edit:before{content:'\e631'}.selectmenu-value{display:inline-block}.selectmenu-value input[type=text]{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:0;display:inline;margin:0;width:6rem}body._keyfocus .selectmenu-value input[type=text]:focus{box-shadow:none}.selectmenu-toggle{padding-right:3rem;background:0 0;border-width:0;bottom:0;float:right;position:absolute;right:0;top:0;width:0}.selectmenu-toggle._active:after,.selectmenu-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.1rem;top:50%;transition:all .2s linear;width:0}._active .selectmenu-toggle:after,.active .selectmenu-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:hover:after{border-color:#000 transparent transparent}.selectmenu-toggle:active,.selectmenu-toggle:focus,.selectmenu-toggle:hover{background:0 0}.selectmenu._active .selectmenu-toggle:before{border-color:#007bdb}body._keyfocus .selectmenu-toggle:focus{box-shadow:none}.selectmenu-toggle:before{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';display:block;position:absolute;right:0;top:0;width:3.2rem}.selectmenu-items{background:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;float:left;left:-1px;margin-top:3px;max-width:20rem;min-width:calc(100% + 2px);position:absolute;top:100%}.selectmenu-items._active{display:block}.selectmenu-items ul{float:left;list-style-type:none;margin:0;min-width:100%;padding:0}.selectmenu-items li{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row;transition:background .2s linear}.selectmenu-items li:hover{background:#e3e3e3}.selectmenu-items li:last-child .selectmenu-item-action,.selectmenu-items li:last-child .selectmenu-item-action:visited{color:#008bdb;text-decoration:none}.selectmenu-items li:last-child .selectmenu-item-action:hover{color:#0fa7ff;text-decoration:underline}.selectmenu-items li:last-child .selectmenu-item-action:active{color:#ff5501;text-decoration:underline}.selectmenu-item{position:relative;width:100%;z-index:1}li._edit>.selectmenu-item{display:none}.selectmenu-item-edit{display:none;padding:.3rem 4rem .3rem .4rem;position:relative;white-space:nowrap;z-index:1}li:last-child .selectmenu-item-edit{padding-right:.4rem}.selectmenu-item-edit .admin__control-text{margin:0;width:5.4rem}li._edit .selectmenu-item-edit{display:block}.selectmenu-item-action{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background:0 0;border:0;color:#333;display:block;font-size:1.4rem;font-weight:400;min-width:100%;padding:1rem 6rem 1rem 1.5rem;text-align:left;transition:background .2s linear;width:5rem}.selectmenu-item-action:focus,.selectmenu-item-action:hover{background:#e3e3e3}.abs-actions-split-xl .action-default,.page-actions .actions-split .action-default{margin-right:4rem}.abs-actions-split-xl .action-toggle,.page-actions .actions-split .action-toggle{padding-right:4rem}.abs-actions-split-xl .action-toggle:after,.page-actions .actions-split .action-toggle:after{border-width:.9rem .6rem 0;margin-top:-.3rem;right:1.4rem}.actions-split{position:relative;z-index:400}.actions-split._active,.actions-split.active,.actions-split:hover{box-shadow:0 0 0 1px #007bdb}.actions-split._active .action-toggle.action-primary,.actions-split._active .action-toggle.primary,.actions-split.active .action-toggle.action-primary,.actions-split.active .action-toggle.primary{background-color:#ba4000;border-color:#ba4000}.actions-split._active .dropdown-menu,.actions-split.active .dropdown-menu{opacity:1;visibility:visible;display:block}.actions-split .action-default,.actions-split .action-toggle{float:left;margin:0}.actions-split .action-default._active,.actions-split .action-default.active,.actions-split .action-default:hover,.actions-split .action-toggle._active,.actions-split .action-toggle.active,.actions-split .action-toggle:hover{box-shadow:none}.actions-split .action-default{margin-right:3.2rem;min-width:9.3rem}.actions-split .action-toggle{padding-right:3.2rem;border-left-color:rgba(0,0,0,.2);bottom:0;padding-left:0;position:absolute;right:0;top:0}.actions-split .action-toggle._active:after,.actions-split .action-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .actions-split .action-toggle:after,.active .actions-split .action-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:hover:after{border-color:#000 transparent transparent}.actions-split .action-toggle.action-primary:after,.actions-split .action-toggle.action-secondary:after,.actions-split .action-toggle.primary:after,.actions-split .action-toggle.secondary:after{border-color:#fff transparent transparent}.actions-split .action-toggle>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-select-wrap{display:inline-block;position:relative}.action-select-wrap .action-select{padding-right:3.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#fff;font-weight:400;text-align:left}.action-select-wrap .action-select._active:after,.action-select-wrap .action-select.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .action-select-wrap .action-select:after,.active .action-select-wrap .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:hover:after{border-color:#000 transparent transparent}.action-select-wrap .action-select:hover,.action-select-wrap .action-select:hover:before{border-color:#878787}.action-select-wrap .action-select:before{background-color:#e3e3e3;border:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:3.2rem}.action-select-wrap .action-select._active{border-color:#007bdb}.action-select-wrap .action-select._active:before{border-color:#007bdb #007bdb #007bdb #adadad}.action-select-wrap .action-select[disabled]{color:#333}.action-select-wrap .action-select[disabled]:after{border-color:#333 transparent transparent}.action-select-wrap._active{z-index:500}.action-select-wrap._active .action-select,.action-select-wrap._active .action-select:before{border-color:#007bdb}.action-select-wrap._active .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .abs-action-menu .action-submenu,.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu,.action-select-wrap .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:45rem;overflow-y:auto}.action-select-wrap .abs-action-menu .action-submenu ._disabled:hover,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .action-menu ._disabled:hover,.action-select-wrap .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled:hover{background:#fff}.action-select-wrap .abs-action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .action-menu ._disabled .action-menu-item,.action-select-wrap .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled .action-menu-item{cursor:default;opacity:.5}.action-select-wrap .action-menu-items{left:0;position:absolute;right:0;top:100%}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu{min-width:100%;position:static}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{position:absolute}.action-multicheck-wrap{display:inline-block;height:1.6rem;padding-top:1px;position:relative;width:3.1rem;z-index:200}.action-multicheck-wrap:hover .action-multicheck-toggle,.action-multicheck-wrap:hover .admin__control-checkbox+label:before{border-color:#878787}.action-multicheck-wrap._active .action-multicheck-toggle,.action-multicheck-wrap._active .admin__control-checkbox+label:before{border-color:#007bdb}.action-multicheck-wrap._active .abs-action-menu .action-submenu,.action-multicheck-wrap._active .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .action-menu,.action-multicheck-wrap._active .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu .action-submenu{opacity:1;visibility:visible;display:block}.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{background-color:#fff}.action-multicheck-wrap._disabled .action-multicheck-toggle,.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{border-color:#adadad;opacity:1}.action-multicheck-wrap .action-multicheck-toggle,.action-multicheck-wrap .admin__control-checkbox,.action-multicheck-wrap .admin__control-checkbox+label{float:left}.action-multicheck-wrap .action-multicheck-toggle{border-radius:0 1px 1px 0;height:1.6rem;margin-left:-1px;padding:0;position:relative;transition:border-color .1s linear;width:1.6rem}.action-multicheck-wrap .action-multicheck-toggle._active:after,.action-multicheck-wrap .action-multicheck-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .action-multicheck-wrap .action-multicheck-toggle:after,.active .action-multicheck-wrap .action-multicheck-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:hover:after{border-color:#000 transparent transparent}.action-multicheck-wrap .action-multicheck-toggle:focus{border-color:#007bdb}.action-multicheck-wrap .action-multicheck-toggle:after{right:.3rem}.action-multicheck-wrap .abs-action-menu .action-submenu,.action-multicheck-wrap .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap .action-menu,.action-multicheck-wrap .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:-1.1rem;margin-top:1px;right:auto;text-align:left}.action-multicheck-wrap .action-menu-item{white-space:nowrap}.admin__action-multiselect-wrap{display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.admin__action-multiselect-wrap.action-select-wrap:focus{box-shadow:none}.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .action-menu,.admin__action-multiselect-wrap.action-select-wrap .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:none;overflow-y:inherit}.admin__action-multiselect-wrap .action-menu-item{transition:background-color .1s linear}.admin__action-multiselect-wrap .action-menu-item._selected{background-color:#e0f6fe}.admin__action-multiselect-wrap .action-menu-item._hover{background-color:#e3e3e3}.admin__action-multiselect-wrap .action-menu-item._unclickable{cursor:default}.admin__action-multiselect-wrap .admin__action-multiselect{border:1px solid #adadad;cursor:pointer;display:block;min-height:3.2rem;padding-right:3.6rem;white-space:normal}.admin__action-multiselect-wrap .admin__action-multiselect:after{bottom:1.25rem;top:auto}.admin__action-multiselect-wrap .admin__action-multiselect:before{height:3.3rem;top:auto}.admin__control-table-wrapper .admin__action-multiselect-wrap{position:static}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect{position:relative}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect:before{right:-1px;top:-1px}.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:34rem;right:auto;top:auto;z-index:1}.admin__action-multiselect-wrap .admin__action-multiselect-item-path{color:#a79d95;font-size:1.2rem;font-weight:400;padding-left:1rem}.admin__action-multiselect-actions-wrap{border-top:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;text-align:center}.admin__action-multiselect-actions-wrap .action-default{font-size:1.3rem;min-width:13rem}.admin__action-multiselect-text{padding:.6rem 1rem}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{text-align:left}.admin__action-multiselect-label{cursor:pointer;position:relative;z-index:1}.admin__action-multiselect-label:before{margin-right:.5rem}._unclickable .admin__action-multiselect-label{cursor:default;font-weight:700}.admin__action-multiselect-search-wrap{border-bottom:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;position:relative}.admin__action-multiselect-search{padding-right:3rem;width:100%}.admin__action-multiselect-search-label{display:block;font-size:1.5rem;height:1em;overflow:hidden;position:absolute;right:2.2rem;top:1.7rem;width:1em}.admin__action-multiselect-search-label:before{content:'\e60c'}.admin__action-multiselect-search-count{color:#a79d95;margin-top:1rem}.admin__action-multiselect-menu-inner{margin-bottom:0;max-height:46rem;overflow-y:auto}.admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{list-style:none;max-height:none;overflow:hidden;padding-left:2.2rem}.admin__action-multiselect-menu-inner ._hidden{display:none}.admin__action-multiselect-crumb{background-color:#f5f5f5;border:1px solid #a79d95;border-radius:1px;display:inline-block;font-size:1.2rem;margin:.3rem -4px .3rem .3rem;padding:.3rem 2.4rem .4rem 1rem;position:relative;transition:border-color .1s linear}.admin__action-multiselect-crumb:hover{border-color:#908379}.admin__action-multiselect-crumb .action-close{bottom:0;font-size:.5em;position:absolute;right:0;top:0;width:2rem}.admin__action-multiselect-crumb .action-close:hover{color:#000}.admin__action-multiselect-crumb .action-close:active,.admin__action-multiselect-crumb .action-close:focus{background-color:transparent}.admin__action-multiselect-crumb .action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__action-multiselect-tree .abs-action-menu .action-submenu,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .action-menu,.admin__action-multiselect-tree .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu{min-width:34.7rem}.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item{margin-top:.1rem}.admin__action-multiselect-tree .action-menu-item{margin-left:4.2rem;position:relative}.admin__action-multiselect-tree .action-menu-item._expended:before{border-left:1px dashed #a79d95;bottom:0;content:'';left:-1rem;position:absolute;top:1rem;width:1px}.admin__action-multiselect-tree .action-menu-item._expended .admin__action-multiselect-dropdown:before{content:'\e615'}.admin__action-multiselect-tree .action-menu-item._with-checkbox .admin__action-multiselect-label{padding-left:2.6rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{padding-left:3.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner:before{left:4.3rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:last-child:before{height:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after,.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{content:'';left:0;position:absolute}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after{border-top:1px dashed #a79d95;height:1px;top:2.1rem;width:5.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{border-left:1px dashed #a79d95;height:100%;top:0;width:1px}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._parent:after{width:4.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root{margin-left:-1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:after{left:3.2rem;width:2.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:before{left:3.2rem;top:1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root._parent:after{display:none}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:first-child:before{top:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:last-child:before{height:1rem}.admin__action-multiselect-tree .admin__action-multiselect-label{line-height:2.2rem;vertical-align:middle;word-break:break-all}.admin__action-multiselect-tree .admin__action-multiselect-label:before{left:0;position:absolute;top:.4rem}.admin__action-multiselect-dropdown{border-radius:50%;height:2.2rem;left:-2.2rem;position:absolute;top:1rem;width:2.2rem;z-index:1}.admin__action-multiselect-dropdown:before{background:#fff;color:#a79d95;content:'\e616';font-size:2.2rem}.admin__actions-switch{display:inline-block;position:relative;vertical-align:middle}.admin__field-control .admin__actions-switch{line-height:3.2rem}.admin__actions-switch+.admin__field-service{min-width:34rem}._disabled .admin__actions-switch-checkbox+.admin__actions-switch-label,.admin__actions-switch-checkbox.disabled+.admin__actions-switch-label{cursor:not-allowed;opacity:.5;pointer-events:none}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:before{left:15px}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:after{background:#79a22e}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label .admin__actions-switch-text:before{content:attr(data-text-on)}.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:after,.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:before{border-color:#007bdb}._error .admin__actions-switch-checkbox+.admin__actions-switch-label:after,._error .admin__actions-switch-checkbox+.admin__actions-switch-label:before{border-color:#e22626}.admin__actions-switch-label{cursor:pointer;display:inline-block;height:22px;line-height:22px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.admin__actions-switch-label:after,.admin__actions-switch-label:before{left:0;position:absolute;right:auto;top:0}.admin__actions-switch-label:before{background:#fff;border:1px solid #aaa6a0;border-radius:100%;content:'';display:block;height:22px;transition:left .2s ease-in 0s;width:22px;z-index:1}.admin__actions-switch-label:after{background:#e3e3e3;border:1px solid #aaa6a0;border-radius:12px;content:'';display:block;height:22px;transition:background .2s ease-in 0s;vertical-align:middle;width:37px;z-index:0}.admin__actions-switch-text:before{content:attr(data-text-off);padding-left:47px;white-space:nowrap}.abs-action-delete,.abs-action-reset,.action-close,.admin__field-fallback-reset,.extensions-information .list .extension-delete,.notifications-close,.search-global-field._active .search-global-action{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0}.abs-action-delete:hover,.abs-action-reset:hover,.action-close:hover,.admin__field-fallback-reset:hover,.extensions-information .list .extension-delete:hover,.notifications-close:hover,.search-global-field._active .search-global-action:hover{background-color:transparent;border:none;box-shadow:none}.abs-action-default,.abs-action-pattern,.abs-action-primary,.abs-action-quaternary,.abs-action-secondary,.abs-action-tertiary,.action-default,.action-primary,.action-quaternary,.action-secondary,.action-tertiary,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions>button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary,button,button.primary,button.secondary,button.tertiary{border:1px solid;border-radius:0;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:1.36;padding:.6rem 1em;text-align:center;vertical-align:baseline}.abs-action-default.disabled,.abs-action-default[disabled],.abs-action-pattern.disabled,.abs-action-pattern[disabled],.abs-action-primary.disabled,.abs-action-primary[disabled],.abs-action-quaternary.disabled,.abs-action-quaternary[disabled],.abs-action-secondary.disabled,.abs-action-secondary[disabled],.abs-action-tertiary.disabled,.abs-action-tertiary[disabled],.action-default.disabled,.action-default[disabled],.action-primary.disabled,.action-primary[disabled],.action-quaternary.disabled,.action-quaternary[disabled],.action-secondary.disabled,.action-secondary[disabled],.action-tertiary.disabled,.action-tertiary[disabled],.modal-popup .modal-footer .action-primary.disabled,.modal-popup .modal-footer .action-primary[disabled],.modal-popup .modal-footer .action-secondary.disabled,.modal-popup .modal-footer .action-secondary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.action-secondary.disabled,.page-actions .page-actions-buttons>button.action-secondary[disabled],.page-actions .page-actions-buttons>button.disabled,.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions .page-actions-buttons>button[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.action-secondary.disabled,.page-actions>button.action-secondary[disabled],.page-actions>button.disabled,.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],.page-actions>button[disabled],button.disabled,button.primary.disabled,button.primary[disabled],button.secondary.disabled,button.secondary[disabled],button.tertiary.disabled,button.tertiary[disabled],button[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-l,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary{font-size:1.6rem;letter-spacing:.025em;padding-bottom:.6875em;padding-top:.6875em}.abs-action-delete,.extensions-information .list .extension-delete{display:inline-block;font-size:1.6rem;margin-left:1.2rem;padding-top:.7rem;text-decoration:none;vertical-align:middle}.abs-action-delete:after,.extensions-information .list .extension-delete:after{color:#666;content:'\e630'}.abs-action-delete:hover:after,.extensions-information .list .extension-delete:hover:after{color:#35302c}.abs-action-button-as-link,.action-advanced,.data-grid .action-delete{line-height:1.36;padding:0;color:#008bdb;text-decoration:none;background:0 0;border:0;display:inline;font-weight:400;border-radius:0}.abs-action-button-as-link:visited,.action-advanced:visited,.data-grid .action-delete:visited{color:#008bdb;text-decoration:none}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{text-decoration:underline}.abs-action-button-as-link:active,.action-advanced:active,.data-grid .action-delete:active{color:#ff5501;text-decoration:underline}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{color:#0fa7ff}.abs-action-button-as-link:active,.abs-action-button-as-link:focus,.abs-action-button-as-link:hover,.action-advanced:active,.action-advanced:focus,.action-advanced:hover,.data-grid .action-delete:active,.data-grid .action-delete:focus,.data-grid .action-delete:hover{background:0 0;border:0}.abs-action-button-as-link.disabled,.abs-action-button-as-link[disabled],.action-advanced.disabled,.action-advanced[disabled],.data-grid .action-delete.disabled,.data-grid .action-delete[disabled],fieldset[disabled] .abs-action-button-as-link,fieldset[disabled] .action-advanced,fieldset[disabled] .data-grid .action-delete{color:#008bdb;opacity:.5;cursor:default;pointer-events:none;text-decoration:underline}.abs-action-button-as-link:active,.abs-action-button-as-link:not(:focus),.action-advanced:active,.action-advanced:not(:focus),.data-grid .action-delete:active,.data-grid .action-delete:not(:focus){box-shadow:none}.abs-action-button-as-link:focus,.action-advanced:focus,.data-grid .action-delete:focus{color:#0fa7ff}.abs-action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.abs-action-default:active,.abs-action-default:focus,.abs-action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.abs-action-primary,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary,button.primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.abs-action-primary:active,.abs-action-primary:focus,.abs-action-primary:hover,.page-actions .page-actions-buttons>button.action-primary:active,.page-actions .page-actions-buttons>button.action-primary:focus,.page-actions .page-actions-buttons>button.action-primary:hover,.page-actions .page-actions-buttons>button.primary:active,.page-actions .page-actions-buttons>button.primary:focus,.page-actions .page-actions-buttons>button.primary:hover,.page-actions>button.action-primary:active,.page-actions>button.action-primary:focus,.page-actions>button.action-primary:hover,.page-actions>button.primary:active,.page-actions>button.primary:focus,.page-actions>button.primary:hover,button.primary:active,button.primary:focus,button.primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-primary.disabled,.abs-action-primary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],button.primary.disabled,button.primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-secondary,.modal-popup .modal-footer .action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions>button.action-secondary,button.secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.abs-action-secondary:active,.abs-action-secondary:focus,.abs-action-secondary:hover,.modal-popup .modal-footer .action-primary:active,.modal-popup .modal-footer .action-primary:focus,.modal-popup .modal-footer .action-primary:hover,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions .page-actions-buttons>button.action-secondary:focus,.page-actions .page-actions-buttons>button.action-secondary:hover,.page-actions>button.action-secondary:active,.page-actions>button.action-secondary:focus,.page-actions>button.action-secondary:hover,button.secondary:active,button.secondary:focus,button.secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-secondary:active,.modal-popup .modal-footer .action-primary:active,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions>button.action-secondary:active,button.secondary:active{background-color:#35302c}.abs-action-tertiary,.modal-popup .modal-footer .action-secondary,button.tertiary{background-color:transparent;border-color:transparent;text-shadow:none;color:#008bdb}.abs-action-tertiary:active,.abs-action-tertiary:focus,.abs-action-tertiary:hover,.modal-popup .modal-footer .action-secondary:active,.modal-popup .modal-footer .action-secondary:focus,.modal-popup .modal-footer .action-secondary:hover,button.tertiary:active,button.tertiary:focus,button.tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#0fa7ff;text-decoration:underline}.abs-action-quaternary,.page-actions .page-actions-buttons>button,.page-actions>button{background-color:transparent;border-color:transparent;text-shadow:none;color:#333}.abs-action-quaternary:active,.abs-action-quaternary:focus,.abs-action-quaternary:hover,.page-actions .page-actions-buttons>button:active,.page-actions .page-actions-buttons>button:focus,.page-actions .page-actions-buttons>button:hover,.page-actions>button:active,.page-actions>button:focus,.page-actions>button:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#1a1a1a}.abs-action-menu,.actions-split .abs-action-menu .action-submenu,.actions-split .abs-action-menu .action-submenu .action-submenu,.actions-split .action-menu,.actions-split .action-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.actions-split .dropdown-menu{text-align:left;background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu._active,.actions-split .abs-action-menu .action-submenu .action-submenu._active,.actions-split .abs-action-menu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .action-menu._active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .actions-split .dropdown-menu .action-submenu._active,.actions-split .dropdown-menu._active{display:block}.abs-action-menu>li,.actions-split .abs-action-menu .action-submenu .action-submenu>li,.actions-split .abs-action-menu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .action-menu>li,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .actions-split .dropdown-menu .action-submenu>li,.actions-split .dropdown-menu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu>li>a:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .abs-action-menu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .action-menu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu>li>a:hover{text-decoration:none}.abs-action-menu>li._visible,.abs-action-menu>li:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu .action-submenu>li:hover,.actions-split .abs-action-menu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .action-menu>li._visible,.actions-split .action-menu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu>li:hover,.actions-split .dropdown-menu>li._visible,.actions-split .dropdown-menu>li:hover{background-color:#e3e3e3}.abs-action-menu>li:active,.actions-split .abs-action-menu .action-submenu .action-submenu>li:active,.actions-split .abs-action-menu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .action-menu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu>li:active,.actions-split .dropdown-menu>li:active{background-color:#cacaca}.abs-action-menu>li._parent,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent,.actions-split .abs-action-menu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .action-menu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent,.actions-split .dropdown-menu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-menu-item,.abs-action-menu .item,.actions-split .abs-action-menu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .item,.actions-split .abs-action-menu .action-submenu .item,.actions-split .action-menu .action-menu-item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .item,.actions-split .action-menu .item,.actions-split .actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .actions-split .dropdown-menu .action-submenu .item,.actions-split .dropdown-menu .action-menu-item,.actions-split .dropdown-menu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu a.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .abs-action-menu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .action-menu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu a.action-menu-item{color:#333}.abs-action-menu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.abs-action-wrap-triangle{position:relative}.abs-action-wrap-triangle .action-default{width:100%}.abs-action-wrap-triangle .action-default:after,.abs-action-wrap-triangle .action-default:before{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.abs-action-wrap-triangle .action-default:active,.abs-action-wrap-triangle .action-default:focus,.abs-action-wrap-triangle .action-default:hover{box-shadow:none}._keyfocus .abs-action-wrap-triangle .action-default:focus{box-shadow:0 0 0 1px #007bdb}.ie10 .abs-action-wrap-triangle .action-default.disabled,.ie10 .abs-action-wrap-triangle .action-default[disabled],.ie9 .abs-action-wrap-triangle .action-default.disabled,.ie9 .abs-action-wrap-triangle .action-default[disabled]{background-color:#fcfcfc;opacity:1;text-shadow:none}.abs-action-wrap-triangle-right{display:inline-block;padding-right:1.6rem;position:relative}.abs-action-wrap-triangle-right .action-default:after,.abs-action-wrap-triangle-right .action-default:before{border-color:transparent transparent transparent #e3e3e3;border-width:1.7rem 0 1.6rem 1.7rem;left:100%;margin-left:-1.7rem}.abs-action-wrap-triangle-right .action-default:before{border-left-color:#949494;right:-1px}.abs-action-wrap-triangle-right .action-default:active:after,.abs-action-wrap-triangle-right .action-default:focus:after,.abs-action-wrap-triangle-right .action-default:hover:after{border-left-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-right .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-right .action-default[disabled]:after{border-color:transparent transparent transparent #fcfcfc}.abs-action-wrap-triangle-right .action-primary:after{border-color:transparent transparent transparent #eb5202}.abs-action-wrap-triangle-right .action-primary:active:after,.abs-action-wrap-triangle-right .action-primary:focus:after,.abs-action-wrap-triangle-right .action-primary:hover:after{border-left-color:#ba4000}.abs-action-wrap-triangle-left{display:inline-block;padding-left:1.6rem}.abs-action-wrap-triangle-left .action-default{text-indent:-.85rem}.abs-action-wrap-triangle-left .action-default:after,.abs-action-wrap-triangle-left .action-default:before{border-color:transparent #e3e3e3 transparent transparent;border-width:1.7rem 1.7rem 1.6rem 0;margin-right:-1.7rem;right:100%}.abs-action-wrap-triangle-left .action-default:before{border-right-color:#949494;left:-1px}.abs-action-wrap-triangle-left .action-default:active:after,.abs-action-wrap-triangle-left .action-default:focus:after,.abs-action-wrap-triangle-left .action-default:hover:after{border-right-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-left .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-left .action-default[disabled]:after{border-color:transparent #fcfcfc transparent transparent}.abs-action-wrap-triangle-left .action-primary:after{border-color:transparent #eb5202 transparent transparent}.abs-action-wrap-triangle-left .action-primary:active:after,.abs-action-wrap-triangle-left .action-primary:focus:after,.abs-action-wrap-triangle-left .action-primary:hover:after{border-right-color:#ba4000}.action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.action-default:active,.action-default:focus,.action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.action-primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.action-primary:active,.action-primary:focus,.action-primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-primary.disabled,.action-primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.action-secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.action-secondary:active,.action-secondary:focus,.action-secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-secondary:active{background-color:#35302c}.action-quaternary,.action-tertiary{background-color:transparent;border-color:transparent;text-shadow:none}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover,.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none}.action-tertiary{color:#008bdb}.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{color:#0fa7ff;text-decoration:underline}.action-quaternary{color:#333}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover{color:#1a1a1a}.action-close>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.action-close:before{content:'\e62f';transition:color .1s linear}.action-close:hover{cursor:pointer;text-decoration:none}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu .action-submenu .action-submenu._active,.abs-action-menu .action-submenu._active,.action-menu .action-submenu._active,.action-menu._active,.actions-split .action-menu .action-submenu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .dropdown-menu .action-submenu._active{display:block}.abs-action-menu .action-submenu .action-submenu>li,.abs-action-menu .action-submenu>li,.action-menu .action-submenu>li,.action-menu>li,.actions-split .action-menu .action-submenu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .dropdown-menu .action-submenu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu .action-submenu .action-submenu>li>a:hover,.abs-action-menu .action-submenu>li>a:hover,.action-menu .action-submenu>li>a:hover,.action-menu>li>a:hover,.actions-split .action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu>li>a:hover{text-decoration:none}.abs-action-menu .action-submenu .action-submenu>li._visible,.abs-action-menu .action-submenu .action-submenu>li:hover,.abs-action-menu .action-submenu>li._visible,.abs-action-menu .action-submenu>li:hover,.action-menu .action-submenu>li._visible,.action-menu .action-submenu>li:hover,.action-menu>li._visible,.action-menu>li:hover,.actions-split .action-menu .action-submenu .action-submenu>li._visible,.actions-split .action-menu .action-submenu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu>li:hover{background-color:#e3e3e3}.abs-action-menu .action-submenu .action-submenu>li:active,.abs-action-menu .action-submenu>li:active,.action-menu .action-submenu>li:active,.action-menu>li:active,.actions-split .action-menu .action-submenu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu>li:active{background-color:#cacaca}.abs-action-menu .action-submenu .action-submenu>li._parent,.abs-action-menu .action-submenu>li._parent,.action-menu .action-submenu>li._parent,.action-menu>li._parent,.actions-split .action-menu .action-submenu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.abs-action-menu .action-submenu>li._parent>.action-menu-item,.action-menu .action-submenu>li._parent>.action-menu-item,.action-menu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .item,.abs-action-menu .action-submenu .item,.action-menu .action-menu-item,.action-menu .action-submenu .action-menu-item,.action-menu .action-submenu .item,.action-menu .item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .item,.actions-split .action-menu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu .action-submenu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu .action-submenu,.ie9 .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .action-menu .action-submenu,.ie9 .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu .action-submenu .action-submenu a.action-menu-item,.abs-action-menu .action-submenu a.action-menu-item,.action-menu .action-submenu a.action-menu-item,.action-menu a.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu a.action-menu-item{color:#333}.abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.abs-action-menu .action-submenu a.action-menu-item:focus,.action-menu .action-submenu a.action-menu-item:focus,.action-menu a.action-menu-item:focus,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.messages .message:last-child{margin:0 0 2rem}.message{background:#fffbbb;border:none;border-radius:0;color:#333;font-size:1.4rem;margin:0 0 1px;padding:1.8rem 4rem 1.8rem 5.5rem;position:relative;text-shadow:none}.message:before{background:0 0;border:0;color:#007bdb;content:'\e61a';font-family:Icons;font-size:1.9rem;font-style:normal;font-weight:400;height:auto;left:1.9rem;line-height:inherit;margin-top:-1.3rem;position:absolute;speak:none;text-shadow:none;top:50%;width:auto}.message-notice:before{color:#007bdb;content:'\e61a'}.message-warning:before{color:#eb5202;content:'\e623'}.message-error{background:#fcc}.message-error:before{color:#e22626;content:'\e632';font-size:1.5rem;left:2.2rem;margin-top:-1rem}.message-success:before{color:#79a22e;content:'\e62d'}.message-spinner:before{display:none}.message-spinner .spinner{font-size:2.5rem;left:1.5rem;position:absolute;top:1.5rem}.message-in-rating-edit{margin-left:1.8rem;margin-right:1.8rem}.modal-popup .action-close,.modal-slide .action-close{color:#736963;position:absolute;right:0;top:0;z-index:1}.modal-popup .action-close:active,.modal-slide .action-close:active{-ms-transform:none;transform:none}.modal-popup .action-close:active:before,.modal-slide .action-close:active:before{font-size:1.8rem}.modal-popup .action-close:hover:before,.modal-slide .action-close:hover:before{color:#58504b}.modal-popup .action-close:before,.modal-slide .action-close:before{font-size:2rem}.modal-popup .action-close:focus,.modal-slide .action-close:focus{background-color:transparent}.modal-popup.prompt .prompt-message{padding:2rem 0}.modal-popup.prompt .prompt-message input{width:100%}.modal-popup.confirm .modal-inner-wrap .message,.modal-popup.prompt .modal-inner-wrap .message{background:#fff}.modal-popup.modal-system-messages .modal-inner-wrap{background:#fffbbb}.modal-popup._image-box .modal-inner-wrap{margin:5rem auto;max-width:78rem;position:static}.modal-popup._image-box .thumbnail-preview{padding-bottom:3rem;text-align:center}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image-block{border:1px solid #ccc;margin:0 auto 2rem;max-width:58rem;padding:2rem}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image{max-height:54rem}.modal-popup .modal-title{font-size:2.4rem;margin-right:6.4rem}.modal-popup .modal-footer{padding-top:2.6rem;text-align:right}.modal-popup .action-close{padding:3rem}.modal-popup .action-close:active,.modal-popup .action-close:focus{background:0 0;padding-right:3.1rem;padding-top:3.1rem}.modal-slide .modal-content-new-attribute{-webkit-overflow-scrolling:touch;overflow:auto;padding-bottom:0}.modal-slide .modal-content-new-attribute iframe{margin-bottom:-2.5rem}.modal-slide .modal-title{font-size:2.1rem;margin-right:5.7rem}.modal-slide .action-close{padding:2.1rem 2.6rem}.modal-slide .action-close:active{padding-right:2.7rem;padding-top:2.2rem}.modal-slide .page-main-actions{margin-bottom:.6rem;margin-top:2.1rem}.modal-slide .magento-message{padding:0 3rem 3rem;position:relative}.modal-slide .magento-message .insert-title-inner,.modal-slide .main-col .insert-title-inner{border-bottom:1px solid #adadad;margin:0 0 2rem;padding-bottom:.5rem}.modal-slide .magento-message .insert-actions,.modal-slide .main-col .insert-actions{float:right}.modal-slide .magento-message .title,.modal-slide .main-col .title{font-size:1.6rem;padding-top:.5rem}.modal-slide .main-col,.modal-slide .side-col{float:left;padding-bottom:0}.modal-slide .main-col:after,.modal-slide .side-col:after{display:none}.modal-slide .side-col{width:20%}.modal-slide .main-col{padding-right:0;width:80%}.modal-slide .content-footer .form-buttons{float:right}.modal-title{font-weight:400;margin-bottom:0;min-height:1em}.modal-title span{font-size:1.4rem;font-style:italic;margin-left:1rem}.spinner{display:inline-block;font-size:4rem;height:1em;margin-right:1.5rem;position:relative;width:1em}.spinner>span:nth-child(1){animation-delay:.27s;-ms-transform:rotate(-315deg);transform:rotate(-315deg)}.spinner>span:nth-child(2){animation-delay:.36s;-ms-transform:rotate(-270deg);transform:rotate(-270deg)}.spinner>span:nth-child(3){animation-delay:.45s;-ms-transform:rotate(-225deg);transform:rotate(-225deg)}.spinner>span:nth-child(4){animation-delay:.54s;-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.spinner>span:nth-child(5){animation-delay:.63s;-ms-transform:rotate(-135deg);transform:rotate(-135deg)}.spinner>span:nth-child(6){animation-delay:.72s;-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.spinner>span:nth-child(7){animation-delay:.81s;-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.spinner>span:nth-child(8){animation-delay:.9;-ms-transform:rotate(0deg);transform:rotate(0deg)}@keyframes fade{0%{background-color:#514943}100%{background-color:#fff}}.spinner>span{-ms-transform:scale(0.4);transform:scale(0.4);animation-name:fade;animation-duration:.72s;animation-iteration-count:infinite;animation-direction:linear;background-color:#fff;border-radius:6px;clip:rect(0 .28571429em .1em 0);height:.1em;margin-top:.5em;position:absolute;width:1em}.ie9 .spinner{background:url(../images/ajax-loader.gif) center no-repeat}.ie9 .spinner>span{display:none}.popup-loading{background:rgba(255,255,255,.8);border-color:#ef672f;color:#ef672f;font-size:14px;font-weight:700;left:50%;margin-left:-100px;padding:100px 0 10px;position:fixed;text-align:center;top:40%;width:200px;z-index:1003}.popup-loading:after{background-image:url(../images/loader-1.gif);content:'';height:64px;left:50%;margin:-32px 0 0 -32px;position:absolute;top:40%;width:64px;z-index:2}.loading-mask,.loading-old{background:rgba(255,255,255,.4);bottom:0;left:0;position:fixed;right:0;top:0;z-index:2003}.loading-mask img,.loading-old img{display:none}.loading-mask p,.loading-old p{margin-top:118px}.loading-mask .loader,.loading-old .loader{background:url(../images/loader-1.gif) 50% 30% no-repeat #f7f3eb;border-radius:5px;bottom:0;color:#575757;font-size:14px;font-weight:700;height:160px;left:0;margin:auto;opacity:.95;position:absolute;right:0;text-align:center;top:0;width:160px}.admin-user{float:right;line-height:1.36;margin-left:.3rem;z-index:490}.admin-user._active .admin__action-dropdown,.admin-user.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin-user .admin__action-dropdown{height:3.3rem;padding:.7rem 2.8rem .4rem 4rem}.admin-user .admin__action-dropdown._active:after,.admin-user .admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:after{border-color:#777 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.3rem;top:50%;transition:all .2s linear;width:0}._active .admin-user .admin__action-dropdown:after,.active .admin-user .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin-user .admin__action-dropdown:before{color:#777;content:'\e600';font-size:2rem;left:1.1rem;margin-top:-1.1rem;position:absolute;top:50%}.admin-user .admin__action-dropdown:hover:before{color:#333}.admin-user .admin__action-dropdown-menu{min-width:20rem;padding-left:1rem;padding-right:1rem}.admin-user .admin__action-dropdown-menu>li>a{padding-left:.5em;padding-right:1.8rem;transition:background-color .1s linear;white-space:nowrap}.admin-user .admin__action-dropdown-menu>li>a:hover{background-color:#e0f6fe;color:#333}.admin-user .admin__action-dropdown-menu>li>a:active{background-color:#c7effd;bottom:-1px;position:relative}.admin-user .admin__action-dropdown-menu .admin-user-name{text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:20rem;overflow:hidden;vertical-align:top}.admin-user-account-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:11.2rem}.search-global{float:right;margin-right:-.3rem;position:relative;z-index:480}.search-global-field{min-width:5rem}.search-global-field._active .search-global-input{background-color:#fff;border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);padding-right:4rem;width:25rem}.search-global-field._active .search-global-action{display:block;height:3.3rem;position:absolute;right:0;text-indent:-100%;top:0;width:5rem;z-index:3}.search-global-field .autocomplete-results{height:3.3rem;position:absolute;right:0;top:0;width:25rem}.search-global-field .search-global-menu{border:1px solid #007bdb;border-top-color:transparent;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin-top:-2px;padding:0;position:absolute;right:0;top:100%;z-index:2}.search-global-field .search-global-menu:after{background-color:#fff;content:'';height:5px;left:0;position:absolute;right:0;top:-5px}.search-global-field .search-global-menu>li{background-color:#fff;border-top:1px solid #ddd;display:block;font-size:1.2rem;padding:.75rem 1.4rem .55rem}.search-global-field .search-global-menu>li._active{background-color:#e0f6fe}.search-global-field .search-global-menu .title{display:block;font-size:1.4rem}.search-global-field .search-global-menu .type{color:#1a1a1a;display:block}.search-global-label{cursor:pointer;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;z-index:2}.search-global-label:active{-ms-transform:scale(0.9);transform:scale(0.9)}.search-global-label:hover:before{color:#000}.search-global-label:before{color:#777;content:'\e60c';font-size:2rem}.search-global-input{background-color:transparent;border:1px solid transparent;font-size:1.4rem;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;transition:all .1s linear,width .3s linear;width:5rem;z-index:1}.search-global-action{display:none}.notifications-wrapper{float:right;line-height:1;position:relative}.notifications-wrapper.active{z-index:500}.notifications-wrapper.active .notifications-action{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.notifications-wrapper.active .notifications-action:after{background-color:#fff;border:none;content:'';display:block;height:6px;left:-6px;margin-top:0;position:absolute;right:0;top:100%;width:auto}.notifications-wrapper .admin__action-dropdown-menu{padding:1rem 0 0;width:32rem}.notifications-action{color:#777;height:3.3rem;padding:.75rem 2rem .65rem}.notifications-action:after{display:none}.notifications-action:before{content:'\e607';font-size:1.9rem;margin-right:0}.notifications-action:active:before{position:relative;top:1px}.notifications-action .notifications-counter{background-color:#e22626;border-radius:1em;color:#fff;display:inline-block;font-size:1.1rem;font-weight:700;left:50%;margin-left:.3em;margin-top:-1.1em;padding:.3em .5em;position:absolute;top:50%}.notifications-entry{line-height:1.36;padding:.6rem 2rem .8rem;position:relative;transition:background-color .1s linear}.notifications-entry:hover{background-color:#e0f6fe}.notifications-entry.notifications-entry-last{margin:0 2rem;padding:.3rem 0 1.3rem;text-align:center}.notifications-entry.notifications-entry-last:hover{background-color:transparent}.notifications-entry+.notifications-entry-last{border-top:1px solid #ddd;padding-bottom:.6rem}.notifications-entry ._cutted{cursor:pointer}.notifications-entry ._cutted .notifications-entry-description-start:after{content:'...'}.notifications-entry-title{color:#ef672f;display:block;font-size:1.1rem;font-weight:700;margin-bottom:.7rem;margin-right:1em}.notifications-entry-description{color:#333;font-size:1.1rem;margin-bottom:.8rem}.notifications-entry-description-end{display:none}.notifications-entry-description-end._show{display:inline}.notifications-entry-time{color:#777;font-size:1.1rem}.notifications-close{line-height:1;padding:1rem;position:absolute;right:0;top:.6rem}.notifications-close:before{color:#ccc;content:'\e620';transition:color .1s linear}.notifications-close:hover:before{color:#b3b3b3}.notifications-close:active{-ms-transform:scale(0.95);transform:scale(0.95)}.page-header-actions{padding-top:1.1rem}.page-header-hgroup{padding-right:1.5rem}.page-title{color:#333;font-size:2.8rem}.page-header{padding:1.5rem 3rem}.menu-wrapper{display:inline-block;position:relative;width:8.8rem;z-index:700}.menu-wrapper:before{background-color:#373330;bottom:0;content:'';left:0;position:fixed;top:0;width:8.8rem;z-index:699}.menu-wrapper._fixed{left:0;position:fixed;top:0}.menu-wrapper._fixed~.page-wrapper{margin-left:8.8rem}.menu-wrapper .logo{display:block;height:8.8rem;padding:2.4rem 0 2.2rem;position:relative;text-align:center;z-index:700}._keyfocus .menu-wrapper .logo:focus{background-color:#4a4542;box-shadow:none}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a{background-color:#373330}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a:after{display:none}.menu-wrapper .logo:hover .logo-img{-webkit-filter:brightness(1.1);filter:brightness(1.1)}.menu-wrapper .logo:active .logo-img{-ms-transform:scale(0.95);transform:scale(0.95)}.menu-wrapper .logo .logo-img{height:4.2rem;transition:-webkit-filter .2s linear,filter .2s linear,transform .1s linear;width:3.5rem}.abs-menu-separator,.admin__menu .item-partners>a:after,.admin__menu .level-0:first-child>a:after{background-color:#736963;content:'';display:block;height:1px;left:0;margin-left:16%;position:absolute;top:0;width:68%}.admin__menu li{display:block}.admin__menu .level-0:first-child>a{position:relative}.admin__menu .level-0._active>a,.admin__menu .level-0:hover>a{color:#f7f3eb}.admin__menu .level-0._active>a{background-color:#524d49}.admin__menu .level-0:hover>a{background-color:#4a4542}.admin__menu .level-0>a{color:#aaa6a0;display:block;font-size:1rem;letter-spacing:.025em;min-height:6.2rem;padding:1.2rem .5rem .5rem;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;transition:background-color .1s linear;word-wrap:break-word;z-index:700}.admin__menu .level-0>a:focus{box-shadow:none}.admin__menu .level-0>a:before{content:'\e63a';display:block;font-size:2.2rem;height:2.2rem}.admin__menu .level-0>.submenu{background-color:#4a4542;box-shadow:0 0 3px #000;left:100%;min-height:calc(8.8rem + 2rem + 100%);padding:2rem 0 0;position:absolute;top:0;-ms-transform:translateX(-100%);transform:translateX(-100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;visibility:hidden;z-index:697}.ie10 .admin__menu .level-0>.submenu,.ie11 .admin__menu .level-0>.submenu{height:100%}.admin__menu .level-0._show>.submenu{-ms-transform:translateX(0);transform:translateX(0);visibility:visible;z-index:698}.admin__menu .level-1{margin-left:1.5rem;margin-right:1.5rem}.admin__menu [class*=level-]:not(.level-0) a{display:block;padding:1.25rem 1.5rem}.admin__menu [class*=level-]:not(.level-0) a:hover{background-color:#403934}.admin__menu [class*=level-]:not(.level-0) a:active{background-color:#322c29;padding-bottom:1.15rem;padding-top:1.35rem}.admin__menu .submenu li{min-width:23.8rem}.admin__menu .submenu a{color:#fcfcfc;transition:background-color .1s linear}.admin__menu .submenu a:focus,.admin__menu .submenu a:hover{box-shadow:none;text-decoration:none}._keyfocus .admin__menu .submenu a:focus{background-color:#403934}._keyfocus .admin__menu .submenu a:active{background-color:#322c29}.admin__menu .submenu .parent{margin-bottom:4.5rem}.admin__menu .submenu .parent .submenu-group-title{color:#a79d95;display:block;font-size:1.6rem;font-weight:600;margin-bottom:.7rem;padding:1.25rem 1.5rem;pointer-events:none}.admin__menu .submenu .column{display:table-cell}.admin__menu .submenu-title{color:#fff;display:block;font-size:2.2rem;font-weight:600;margin-bottom:4.2rem;margin-left:3rem;margin-right:5.8rem}.admin__menu .submenu-sub-title{color:#fff;display:block;font-size:1.2rem;margin:-3.8rem 5.8rem 3.8rem 3rem}.admin__menu .action-close{padding:2.4rem 2.8rem;position:absolute;right:0;top:0}.admin__menu .action-close:before{color:#a79d95;font-size:1.7rem}.admin__menu .action-close:hover:before{color:#fff}.admin__menu .item-dashboard>a:before{content:'\e604';font-size:1.8rem;padding-top:.4rem}.admin__menu .item-sales>a:before{content:'\e60b'}.admin__menu .item-catalog>a:before{content:'\e608'}.admin__menu .item-customer>a:before{content:'\e603';font-size:2.6rem;position:relative;top:-.4rem}.admin__menu .item-marketing>a:before{content:'\e609';font-size:2rem;padding-top:.2rem}.admin__menu .item-content>a:before{content:'\e602';font-size:2.4rem;position:relative;top:-.2rem}.admin__menu .item-report>a:before{content:'\e60a'}.admin__menu .item-stores>a:before{content:'\e60d';font-size:1.9rem;padding-top:.3rem}.admin__menu .item-system>a:before{content:'\e610'}.admin__menu .item-partners._active>a:after,.admin__menu .item-system._current+.item-partners>a:after{display:none}.admin__menu .item-partners>a{padding-bottom:1rem}.admin__menu .item-partners>a:before{content:'\e612'}.admin__menu .level-0>.submenu>ul>.level-1:only-of-type>.submenu-group-title,.admin__menu .submenu .column:only-of-type .submenu-group-title{display:none}.admin__menu-overlay{bottom:0;left:0;position:fixed;right:0;top:0;z-index:697}.store-switcher{color:#333;float:left;font-size:1.3rem;margin-top:.7rem}.store-switcher .admin__action-dropdown{background-color:#f8f8f8;margin-left:.5em}.store-switcher .dropdown{display:inline-block;position:relative}.store-switcher .dropdown:after,.store-switcher .dropdown:before{content:'';display:table}.store-switcher .dropdown:after{clear:both}.store-switcher .dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e607';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle:active:after,.store-switcher .dropdown .action.toggle:hover:after{color:#333}.store-switcher .dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle.active:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e618';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle.active:active:after,.store-switcher .dropdown .action.toggle.active:hover:after{color:#333}.store-switcher .dropdown .dropdown-menu{margin:4px 0 0;padding:0;list-style:none;background:#fff;border:1px solid #aaa6a0;min-width:19.5rem;z-index:100;box-sizing:border-box;display:none;position:absolute;top:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.store-switcher .dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .dropdown .dropdown-menu li:hover{background:0 0;cursor:pointer}.store-switcher .dropdown.active{overflow:visible}.store-switcher .dropdown.active .dropdown-menu{display:block}.store-switcher .dropdown-menu{left:0;margin-top:.5em;max-height:250px;overflow-y:auto;padding-top:.25em}.store-switcher .dropdown-menu li{border:0;cursor:default}.store-switcher .dropdown-menu li:hover{cursor:default}.store-switcher .dropdown-menu li a,.store-switcher .dropdown-menu li span{color:#333;display:block;padding:.5rem 1.3rem}.store-switcher .dropdown-menu li a{text-decoration:none}.store-switcher .dropdown-menu li a:hover{background:#e9e9e9}.store-switcher .dropdown-menu li span{color:#adadad;cursor:default}.store-switcher .dropdown-menu li.current span{background:#eee;color:#333}.store-switcher .dropdown-menu .store-switcher-store a,.store-switcher .dropdown-menu .store-switcher-store span{padding-left:2.6rem}.store-switcher .dropdown-menu .store-switcher-store-view a,.store-switcher .dropdown-menu .store-switcher-store-view span{padding-left:3.9rem}.store-switcher .dropdown-menu .dropdown-toolbar{border-top:1px solid #ebebeb;margin-top:1rem}.store-switcher .dropdown-menu .dropdown-toolbar a:before{content:'\e610';margin-right:.25em;position:relative;top:1px}.store-switcher-label{font-weight:700}.store-switcher-alt{display:inline-block;position:relative}.store-switcher-alt.active .dropdown-menu{display:block}.store-switcher-alt .dropdown-menu{margin-top:2px;white-space:nowrap}.store-switcher-alt .dropdown-menu ul{list-style:none;margin:0;padding:0}.store-switcher-alt strong{color:#a79d95;display:block;font-size:14px;font-weight:500;line-height:1.333;padding:5px 10px}.store-switcher-alt .store-selected{color:#676056;cursor:pointer;font-size:12px;font-weight:400;line-height:1.333}.store-switcher-alt .store-selected:after{-webkit-font-smoothing:antialiased;color:#afadac;content:'\e02c';font-style:normal;font-weight:400;margin:0 0 0 3px;speak:none;vertical-align:text-top}.store-switcher-alt .store-switcher-store,.store-switcher-alt .store-switcher-website{padding:0}.store-switcher-alt .store-switcher-store:hover,.store-switcher-alt .store-switcher-website:hover{background:0 0}.store-switcher-alt .manage-stores,.store-switcher-alt .store-switcher-all,.store-switcher-alt .store-switcher-store-view{padding:0}.store-switcher-alt .manage-stores>a,.store-switcher-alt .store-switcher-all>a{color:#676056;display:block;font-size:12px;padding:8px 15px;text-decoration:none}.store-switcher-website{margin:5px 0 0}.store-switcher-website>strong{padding-left:13px}.store-switcher-store{margin:1px 0 0}.store-switcher-store>strong{padding-left:20px}.store-switcher-store>ul{margin-top:1px}.store-switcher-store-view:first-child{border-top:1px solid #e5e5e5}.store-switcher-store-view>a{color:#333;display:block;font-size:13px;padding:5px 15px 5px 24px;text-decoration:none}.store-view:not(.store-switcher){float:left}.store-view .store-switcher-label{display:inline-block;margin-top:1rem}.tooltip{margin-left:.5em}.tooltip .help a,.tooltip .help span{cursor:pointer;display:inline-block;height:22px;position:relative;vertical-align:middle;width:22px;z-index:2}.tooltip .help a:before,.tooltip .help span:before{color:#333;content:'\e633';font-size:1.7rem}.tooltip .help a:hover{text-decoration:none}.tooltip .tooltip-content{background:#000;border-radius:3px;color:#fff;display:none;margin-left:-19px;margin-top:10px;max-width:200px;padding:4px 8px;position:absolute;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{border-bottom:5px solid #000;border-left:5px solid transparent;border-right:5px solid transparent;content:'';height:0;left:20px;opacity:.8;position:absolute;top:-5px;width:0}.tooltip .tooltip-content.loading{position:absolute}.tooltip .tooltip-content.loading:before{border-bottom-color:rgba(0,0,0,.3)}.tooltip:hover>.tooltip-content{display:block}.page-actions._fixed,.page-main-actions:not(._hidden){background:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;padding:1.5rem}.page-main-actions{margin:0 0 3rem}.page-main-actions._hidden .store-switcher{display:none}.page-main-actions._hidden .page-actions-placeholder{min-height:50px}.page-actions{float:right}.page-main-actions .page-actions._fixed{left:8.8rem;position:fixed;right:0;top:0;z-index:501}.page-main-actions .page-actions._fixed .page-actions-inner:before{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333;content:attr(data-title);float:left;font-size:2.8rem;margin-top:.3rem;max-width:50%}.page-actions .page-actions-buttons>button,.page-actions>button{float:right;margin-left:1.3rem}.page-actions .page-actions-buttons>button.action-back,.page-actions .page-actions-buttons>button.back,.page-actions>button.action-back,.page-actions>button.back{float:left;-ms-flex-order:-1;order:-1}.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before{content:'\e626';margin-right:.5em;position:relative;top:1px}.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary{-ms-flex-order:2;order:2}.page-actions .page-actions-buttons>button.save:not(.primary),.page-actions>button.save:not(.primary){-ms-flex-order:1;order:1}.page-actions .page-actions-buttons>button.delete,.page-actions>button.delete{-ms-flex-order:-1;order:-1}.page-actions .actions-split{float:right;margin-left:1.3rem;-ms-flex-order:2;order:2}.page-actions .actions-split .dropdown-menu .item{display:block}.page-actions-buttons{float:right;-ms-flex-pack:end;justify-content:flex-end;display:-ms-flexbox;display:flex}.customer-index-edit .page-actions-buttons{background-color:transparent}.admin__page-nav{background:#f1f1f1;border:1px solid #e3e3e3}.admin__page-nav._collapsed:first-child{border-bottom:none}.admin__page-nav._collapsed._show{border-bottom:1px solid #e3e3e3}.admin__page-nav._collapsed._show ._collapsible{background:#f1f1f1}.admin__page-nav._collapsed._show ._collapsible:after{content:'\e62b'}.admin__page-nav._collapsed._show ._collapsible+.admin__page-nav-items{display:block}.admin__page-nav._collapsed._hide .admin__page-nav-title-messages,.admin__page-nav._collapsed._hide .admin__page-nav-title-messages ._active{display:inline-block}.admin__page-nav+._collapsed{border-bottom:none;border-top:none}.admin__page-nav-title{border-bottom:1px solid #e3e3e3;color:#303030;display:block;font-size:1.4rem;line-height:1.2;margin:0 0 -1px;padding:1.8rem 1.5rem;position:relative;text-transform:uppercase}.admin__page-nav-title._collapsible{background:#fff;cursor:pointer;margin:0;padding-right:3.5rem;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-title._collapsible+.admin__page-nav-items{display:none;margin-top:-1px}.admin__page-nav-title._collapsible:after{content:'\e628';font-size:1.3rem;font-weight:700;position:absolute;right:1.8rem;top:2rem}.admin__page-nav-title._collapsible:hover{background:#f1f1f1}.admin__page-nav-title._collapsible:last-child{margin:0 0 -1px}.admin__page-nav-title strong{font-weight:700}.admin__page-nav-title .admin__page-nav-title-messages{display:none}.admin__page-nav-items{list-style-type:none;margin:0;padding:1rem 0 1.3rem}.admin__page-nav-item{border-left:3px solid transparent;margin-left:.7rem;padding:0;position:relative;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-item:hover{border-color:#e4e4e4}.admin__page-nav-item:hover .admin__page-nav-link{background:#e4e4e4;color:#303030;text-decoration:none}.admin__page-nav-item._active,.admin__page-nav-item.ui-state-active{border-color:#eb5202}.admin__page-nav-item._active .admin__page-nav-link,.admin__page-nav-item.ui-state-active .admin__page-nav-link{background:#fff;border-color:#e3e3e3;border-right:1px solid #fff;color:#303030;margin-right:-1px;font-weight:600}.admin__page-nav-item._loading:before,.admin__page-nav-item.ui-tabs-loading:before{display:none}.admin__page-nav-item._loading .admin__page-nav-item-message-loader,.admin__page-nav-item.ui-tabs-loading .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-link{border:1px solid transparent;border-width:1px 0;color:#303030;display:block;font-weight:500;line-height:1.2;margin:0 0 -1px;padding:2rem 4rem 2rem 1rem;transition:border-color .1s ease-out,background-color .1s ease-out;word-wrap:break-word}.admin__page-nav-item-messages{display:inline-block}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-size:1.4rem;font-weight:400;left:-1rem;line-height:1.36;padding:1.5rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after,.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf;margin-top:1px}.admin__page-nav-item-message-loader{display:none;margin-top:-1rem;position:absolute;right:0;top:50%}.admin__page-nav-item-message-loader .spinner{font-size:2rem;margin-right:1.5rem}._loading>.admin__page-nav-item-messages .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-item-message{position:relative}.admin__page-nav-item-message:hover{z-index:500}.admin__page-nav-item-message:hover .admin__page-nav-item-message-tooltip{display:block}.admin__page-nav-item-message._changed,.admin__page-nav-item-message._error{display:none}.admin__page-nav-item-message .admin__page-nav-item-message-icon{display:inline-block;font-size:1.4rem;padding-left:.8em;vertical-align:baseline}.admin__page-nav-item-message .admin__page-nav-item-message-icon:after{color:#666;content:'\e631'}._changed:not(._error)>.admin__page-nav-item-messages ._changed{display:inline-block}._error .admin__page-nav-item-message-icon:after{color:#eb5202;content:'\e623'}._error>.admin__page-nav-item-messages ._error{display:inline-block}._error>.admin__page-nav-item-messages ._error .spinner{font-size:2rem;margin-right:1.5rem}._error .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;left:-1rem;line-height:1.36;padding:2rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}._error .admin__page-nav-item-message-tooltip:after,._error .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}._error .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}._error .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf}.admin__data-grid-wrap-static .data-grid{box-sizing:border-box}.admin__data-grid-wrap-static .data-grid thead{color:#333}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td{background-color:#f5f5f5}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td._dragging{background-color:rgba(245,245,245,.95)}.admin__data-grid-wrap-static .data-grid ul{margin-left:1rem;padding-left:1rem}.admin__data-grid-wrap-static .admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-wrap-static .admin__data-grid-loading-mask .grid-loader{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-filters-actions-wrap{float:right}.data-grid-search-control-wrap{float:left;max-width:45.5rem;position:relative;width:35%}.data-grid-search-control-wrap :-ms-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-webkit-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-moz-placeholder{font-style:italic}.data-grid-search-control-wrap .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:.6rem 2rem .2rem;position:absolute;right:0;top:1px}.data-grid-search-control-wrap .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.data-grid-search-control-wrap .action-submit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.data-grid-search-control-wrap .action-submit:hover:before{color:#1a1a1a}._keyfocus .data-grid-search-control-wrap .action-submit:focus{box-shadow:0 0 0 1px #008bdb}.data-grid-search-control-wrap .action-submit:before{content:'\e60c';font-size:2rem;transition:color .1s linear}.data-grid-search-control-wrap .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.data-grid-search-control-wrap .abs-action-menu .action-submenu,.data-grid-search-control-wrap .abs-action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .action-menu,.data-grid-search-control-wrap .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:19.25rem;overflow-y:auto;z-index:398}.data-grid-search-control-wrap .action-menu-item._selected{background-color:#e0f6fe}.data-grid-search-control-wrap .data-grid-search-label{display:none}.data-grid-search-control{padding-right:6rem;width:100%}.data-grid-filters-action-wrap{float:left;padding-left:2rem}.data-grid-filters-action-wrap .action-default{font-size:1.3rem;margin-bottom:1rem;padding-left:1.7rem;padding-right:2.1rem;padding-top:.7rem}.data-grid-filters-action-wrap .action-default._active{background-color:#fff;border-bottom-color:#fff;border-right-color:#ccc;font-weight:600;margin:-.1rem 0 0;padding-bottom:1.6rem;padding-top:.8rem;position:relative;z-index:281}.data-grid-filters-action-wrap .action-default._active:after{background-color:#eb5202;bottom:100%;content:'';height:3px;left:-1px;position:absolute;right:-1px}.data-grid-filters-action-wrap .action-default:before{color:#333;content:'\e605';font-size:1.8rem;margin-right:.4rem;position:relative;top:-1px;vertical-align:top}.data-grid-filters-action-wrap .filters-active{display:none}.admin__action-grid-select .admin__control-select{margin:-.5rem .5rem 0 0;padding-bottom:.6rem;padding-top:.6rem}.admin__data-grid-filters-wrap{opacity:0;visibility:hidden;clear:both;font-size:1.3rem;transition:opacity .3s ease}.admin__data-grid-filters-wrap._show{opacity:1;visibility:visible;border-bottom:1px solid #ccc;border-top:1px solid #ccc;margin-bottom:.7rem;padding:3.6rem 0 3rem;position:relative;top:-1px;z-index:280}.admin__data-grid-filters-wrap._show .admin__data-grid-filters,.admin__data-grid-filters-wrap._show .admin__data-grid-filters-footer{display:block}.admin__data-grid-filters-wrap .admin__form-field-label,.admin__data-grid-filters-wrap .admin__form-field-legend{display:block;font-weight:700;margin:0 0 .3rem;text-align:left}.admin__data-grid-filters-wrap .admin__form-field{display:inline-block;margin-bottom:2em;margin-left:0;padding-left:2rem;padding-right:2rem;vertical-align:top;width:calc(100% / 4 - 4px)}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field{display:block;float:none;margin-bottom:1.5rem;padding-left:0;padding-right:0;width:auto}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field:last-child{margin-bottom:0}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-label{border:1px solid transparent;float:left;font-weight:400;line-height:1.36;margin-bottom:0;padding-bottom:.6rem;padding-right:1em;padding-top:.6rem;width:25%}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-control{margin-left:25%}.admin__data-grid-filters-wrap .admin__action-multiselect,.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text,.admin__data-grid-filters-wrap .admin__form-field-label{font-size:1.3rem}.admin__data-grid-filters-wrap .admin__control-select{height:3.2rem;padding-top:.5rem}.admin__data-grid-filters-wrap .admin__action-multiselect:before{height:3.2rem;width:3.2rem}.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text._has-datepicker{width:100%}.admin__data-grid-filters{display:none;margin-left:-2rem;margin-right:-2rem}.admin__filters-legend{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-filters-footer{display:none;font-size:1.4rem}.admin__data-grid-filters-footer .admin__footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-filters-footer .admin__footer-secondary-actions{float:left;width:50%}.admin__data-grid-filters-current{border-bottom:.1rem solid #ccc;border-top:.1rem solid #ccc;display:none;font-size:1.3rem;margin-bottom:.9rem;padding-bottom:.8rem;padding-top:1.1rem;width:100%}.admin__data-grid-filters-current._show{display:table;position:relative;top:-1px;z-index:3}.admin__data-grid-filters-current._show+.admin__data-grid-filters-wrap._show{margin-top:-1rem}.admin__current-filters-actions-wrap,.admin__current-filters-list-wrap,.admin__current-filters-title-wrap{display:table-cell;vertical-align:top}.admin__current-filters-title{margin-right:1em;white-space:nowrap}.admin__current-filters-list-wrap{width:100%}.admin__current-filters-list{margin-bottom:0}.admin__current-filters-list>li{display:inline-block;font-weight:600;margin:0 1rem .5rem;padding-right:2.6rem;position:relative}.admin__current-filters-list .action-remove{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0;line-height:1;position:absolute;right:0;top:1px}.admin__current-filters-list .action-remove:hover{background-color:transparent;border:none;box-shadow:none}.admin__current-filters-list .action-remove:hover:before{color:#949494}.admin__current-filters-list .action-remove:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__current-filters-list .action-remove:before{color:#adadad;content:'\e620';font-size:1.6rem;transition:color .1s linear}.admin__current-filters-list .action-remove>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__current-filters-actions-wrap .action-clear{border:none;padding-bottom:0;padding-top:0;white-space:nowrap}.admin__data-grid-pager-wrap{float:right;text-align:right}.admin__data-grid-pager{display:inline-block;margin-left:3rem}.admin__data-grid-pager .admin__control-text::-webkit-inner-spin-button,.admin__data-grid-pager .admin__control-text::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.admin__data-grid-pager .admin__control-text{-moz-appearance:textfield;text-align:center;width:4.4rem}.action-next,.action-previous{width:4.4rem}.action-next:before,.action-previous:before{font-weight:700}.action-next>span,.action-previous>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-previous{margin-right:2.5rem;text-indent:-.25em}.action-previous:before{content:'\e629'}.action-next{margin-left:1.5rem;text-indent:.1em}.action-next:before{content:'\e62a'}.admin__data-grid-action-bookmarks{opacity:.98}.admin__data-grid-action-bookmarks .admin__action-dropdown-text:after{left:0;right:-6px}.admin__data-grid-action-bookmarks._active{z-index:290}.admin__data-grid-action-bookmarks .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:15rem;min-width:4.9rem;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown:before{content:'\e60f'}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu{font-size:1.3rem;left:0;padding:1rem 0;right:auto}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li{padding:0 5rem 0 0;position:relative;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action){transition:background-color .1s linear}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action):hover{background-color:#e3e3e3}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item{max-width:23rem;min-width:18rem;white-space:normal;word-break:break-all}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit{display:none;padding-bottom:1rem;padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit .action-dropdown-menu-item-actions{padding-bottom:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action{padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action+.action-dropdown-menu-item-last{padding-top:.5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a{color:#008bdb;text-decoration:none;display:inline-block;padding-left:1.1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a:hover{color:#0fa7ff;text-decoration:underline}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-last{padding-bottom:0}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item{display:none}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item-edit{display:block}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._active .action-dropdown-menu-link{font-weight:600}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{font-size:1.3rem;min-width:15rem;width:calc(100% - 4rem)}.ie9 .admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{width:15rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-actions{border-left:1px solid #fff;bottom:0;position:absolute;right:0;top:0;width:5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-link{color:#333;display:block;text-decoration:none;padding:1rem 1rem 1rem 2.1rem}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit,.admin__data-grid-action-bookmarks .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;vertical-align:top}.admin__data-grid-action-bookmarks .action-delete:hover,.admin__data-grid-action-bookmarks .action-edit:hover,.admin__data-grid-action-bookmarks .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before{font-size:1.7rem}.admin__data-grid-action-bookmarks .action-delete>span,.admin__data-grid-action-bookmarks .action-edit>span,.admin__data-grid-action-bookmarks .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit{padding:.6rem 1.4rem}.admin__data-grid-action-bookmarks .action-delete:active,.admin__data-grid-action-bookmarks .action-edit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__data-grid-action-bookmarks .action-submit{padding:.6rem 1rem .6rem .8rem}.admin__data-grid-action-bookmarks .action-submit:active{position:relative;right:-1px}.admin__data-grid-action-bookmarks .action-submit:before{content:'\e625'}.admin__data-grid-action-bookmarks .action-delete:before{content:'\e630'}.admin__data-grid-action-bookmarks .action-edit{padding-top:.8rem}.admin__data-grid-action-bookmarks .action-edit:before{content:'\e631'}.admin__data-grid-action-columns._active{opacity:.98;z-index:290}.admin__data-grid-action-columns .admin__action-dropdown:before{content:'\e610';font-size:1.8rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-columns-menu{color:#303030;font-size:1.3rem;overflow:hidden;padding:2.2rem 3.5rem 1rem;z-index:1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-header{border-bottom:1px solid #d1d1d1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-content{width:49.2rem}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-footer{border-top:1px solid #d1d1d1;padding-top:2.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content{max-height:22.85rem;overflow-y:auto;padding-top:1.5rem;position:relative;width:47.4rem}.admin__data-grid-action-columns-menu .admin__field-option{float:left;height:1.9rem;margin-bottom:1.5rem;padding:0 1rem 0 0;width:15.8rem}.admin__data-grid-action-columns-menu .admin__field-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-header{padding-bottom:1.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-footer{padding:1rem 0 2rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-secondary-actions{float:left;margin-left:-1em}.admin__data-grid-action-export._active{opacity:.98;z-index:290}.admin__data-grid-action-export .admin__action-dropdown:before{content:'\e635';font-size:1.7rem;left:.3rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-export-menu{padding-left:2rem;padding-right:2rem;padding-top:1rem}.admin__data-grid-action-export-menu .admin__action-dropdown-footer-main-actions{padding-bottom:2rem;padding-top:2.5rem;white-space:nowrap}.sticky-header{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:8.8rem;margin-top:-1px;padding:.5rem 3rem 0;position:fixed;right:0;top:77px;z-index:398}.sticky-header .admin__data-grid-wrap{margin-bottom:0;overflow-x:visible;padding-bottom:0}.sticky-header .admin__data-grid-header-row{position:relative;text-align:right}.sticky-header .admin__data-grid-header-row:last-child{margin:0}.sticky-header .admin__data-grid-actions-wrap,.sticky-header .admin__data-grid-filters-wrap,.sticky-header .admin__data-grid-pager-wrap,.sticky-header .data-grid-filters-actions-wrap,.sticky-header .data-grid-search-control-wrap{display:inline-block;float:none;vertical-align:top}.sticky-header .action-select-wrap{float:left;margin-right:1.5rem;width:16.66666667%}.sticky-header .admin__control-support-text{float:left}.sticky-header .data-grid-search-control-wrap{margin:-.5rem 0 0 1.1rem;width:auto}.sticky-header .data-grid-search-control-wrap .data-grid-search-label{box-sizing:border-box;cursor:pointer;display:block;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;position:relative;text-align:center}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before{color:#333;content:'\e60c';font-size:2rem;transition:color .1s linear}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:hover:before{color:#000}.sticky-header .data-grid-search-control-wrap .data-grid-search-label span{display:none}.sticky-header .data-grid-filters-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-left:0;position:relative}.sticky-header .data-grid-filters-actions-wrap .action-default{background-color:transparent;border:1px solid transparent;box-sizing:border-box;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;text-align:center;transition:all .15s ease}.sticky-header .data-grid-filters-actions-wrap .action-default span{display:none}.sticky-header .data-grid-filters-actions-wrap .action-default:before{margin:0}.sticky-header .data-grid-filters-actions-wrap .action-default._active{background-color:#fff;border-color:#adadad #adadad #fff;box-shadow:1px 1px 5px rgba(0,0,0,.5);z-index:210}.sticky-header .data-grid-filters-actions-wrap .action-default._active:after{background-color:#fff;content:'';height:6px;left:-2px;position:absolute;right:-6px;top:100%}.sticky-header .data-grid-filters-action-wrap{padding:0}.sticky-header .admin__data-grid-filters-wrap{background-color:#fff;border:1px solid #adadad;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:0;padding-left:3.5rem;padding-right:3.5rem;position:absolute;top:100%;width:100%;z-index:209}.sticky-header .admin__data-grid-filters-current+.admin__data-grid-filters-wrap._show{margin-top:-6px}.sticky-header .filters-active{background-color:#e04f00;border-radius:10px;color:#fff;display:block;font-size:1.4rem;font-weight:700;padding:.1rem .7rem;position:absolute;right:-7px;top:0;z-index:211}.sticky-header .filters-active:empty{padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-right:.3rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown{background-color:transparent;box-sizing:border-box;min-width:3.8rem;padding-left:.6rem;padding-right:.6rem;text-align:center}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:0;min-width:0;overflow:hidden}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:before{margin:0}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap{margin-right:1.1rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after,.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:after{display:none}.sticky-header .admin__data-grid-actions-wrap ._active .admin__action-dropdown{background-color:#fff}.sticky-header .admin__data-grid-action-bookmarks .admin__action-dropdown:before{position:relative;top:-3px}.sticky-header .admin__data-grid-filters-current{border-bottom:0;border-top:0;margin-bottom:0;padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-pager .admin__control-text,.sticky-header .admin__data-grid-pager-wrap .admin__control-support-text,.sticky-header .data-grid-search-control-wrap .action-submit,.sticky-header .data-grid-search-control-wrap .data-grid-search-control{display:none}.sticky-header .action-next{margin:0}.sticky-header .data-grid{margin-bottom:-1px}.data-grid-cap-left,.data-grid-cap-right{background-color:#f8f8f8;bottom:-2px;position:absolute;top:6rem;width:3rem;z-index:201}.data-grid-cap-left{left:0}.admin__data-grid-header{font-size:1.4rem}.admin__data-grid-header-row+.admin__data-grid-header-row{margin-top:1.1rem}.admin__data-grid-header-row:last-child{margin-bottom:0}.admin__data-grid-header-row .action-select-wrap{display:block}.admin__data-grid-header-row .action-select{width:100%}.admin__data-grid-actions-wrap{float:right;margin-left:1.1rem;margin-top:-.5rem;text-align:right}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap{position:relative;text-align:left;vertical-align:middle}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._hide+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:first-child:after{display:none}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown-menu{border-color:#adadad}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after{border-left:1px solid #ccc;content:'';height:3.2rem;left:0;position:absolute;top:.5rem;z-index:3}.admin__data-grid-actions-wrap .admin__action-dropdown{padding-bottom:1.7rem;padding-top:1.2rem}.admin__data-grid-actions-wrap .admin__action-dropdown:after{margin-top:-.4rem}.admin__data-grid-outer-wrap{min-height:8rem;position:relative}.admin__data-grid-wrap{margin-bottom:2rem;max-width:100%;overflow-x:auto;padding-bottom:1rem;padding-top:2rem}.admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-loading-mask .spinner{font-size:4rem;left:50%;margin-left:-2rem;margin-top:-2rem;position:absolute;top:50%}.ie9 .admin__data-grid-loading-mask .spinner{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-cell-content{display:inline-block;overflow:hidden;width:100%}body._in-resize{cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body._in-resize *,body._in-resize .data-grid-th,body._in-resize .data-grid-th._draggable,body._in-resize .data-grid-th._sortable{cursor:col-resize!important}._layout-fixed{table-layout:fixed}.data-grid{border:none;font-size:1.3rem;margin-bottom:0;width:100%}.data-grid:not(._dragging-copy) ._odd-row td._dragging{background-color:#d0d0d0}.data-grid:not(._dragging-copy) ._dragging{background-color:#d9d9d9;color:rgba(48,48,48,.95)}.data-grid:not(._dragging-copy) ._dragging a{color:rgba(0,139,219,.95)}.data-grid:not(._dragging-copy) ._dragging a:hover{color:rgba(15,167,255,.95)}.data-grid._dragged{outline:#007bdb solid 1px}.data-grid thead{background-color:transparent}.data-grid tfoot th{padding:1rem}.data-grid tr._odd-row td{background-color:#f5f5f5}.data-grid tr._odd-row td._update-status-active{background:#89e1ff}.data-grid tr._odd-row td._update-status-upcoming{background:#b7ee63}.data-grid tr:hover td._update-status-active,.data-grid tr:hover td._update-status-upcoming{background-color:#e5f7fe}.data-grid tr.data-grid-tr-no-data td{font-size:1.6rem;padding:3rem;text-align:center}.data-grid tr.data-grid-tr-no-data:hover td{background-color:#fff;cursor:default}.data-grid tr:active td{background-color:#e0f6fe}.data-grid tr:hover td{background-color:#e5f7fe}.data-grid tr._dragged td{background:#d0d0d0}.data-grid tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.data-grid tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.data-grid tr:not(.data-grid-editable-row):last-child td{border-bottom:.1rem solid #d6d6d6}.data-grid tr ._clickable,.data-grid tr._clickable{cursor:pointer}.data-grid tr._disabled{pointer-events:none}.data-grid td,.data-grid th{font-size:1.3rem;line-height:1.36;transition:background-color .1s linear;vertical-align:top}.data-grid td._resizing,.data-grid th._resizing{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid td._hidden,.data-grid th._hidden{display:none}.data-grid td._fit,.data-grid th._fit{width:1%}.data-grid td{background-color:#fff;border-left:.1rem dashed #d6d6d6;border-right:.1rem dashed #d6d6d6;color:#303030;padding:1rem}.data-grid td:first-child{border-left-style:solid}.data-grid td:last-child{border-right-style:solid}.data-grid td .action-select-wrap{position:static}.data-grid td .action-select{color:#008bdb;text-decoration:none;background-color:transparent;border:none;font-size:1.3rem;padding:0 3rem 0 0;position:relative}.data-grid td .action-select:hover{color:#0fa7ff;text-decoration:underline}.data-grid td .action-select:hover:after{border-color:#0fa7ff transparent transparent}.data-grid td .action-select:after{border-color:#008bdb transparent transparent;margin:.6rem 0 0 .7rem;right:auto;top:auto}.data-grid td .action-select:before{display:none}.data-grid td .abs-action-menu .action-submenu,.data-grid td .abs-action-menu .action-submenu .action-submenu,.data-grid td .action-menu,.data-grid td .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:10rem;right:0;text-align:left;top:auto;z-index:1}.data-grid td._update-status-active{background:#bceeff}.data-grid td._update-status-upcoming{background:#ccf391}.data-grid th{background-color:#514943;border:.1rem solid #8a837f;border-left-color:transparent;color:#fff;font-weight:600;padding:0;text-align:left}.data-grid th:first-child{border-left-color:#8a837f}.data-grid th._dragover-left{box-shadow:inset 3px 0 0 0 #fff;z-index:2}.data-grid th._dragover-right{box-shadow:inset -3px 0 0 0 #fff}.data-grid .shadow-div{cursor:col-resize;height:100%;margin-right:-5px;position:absolute;right:0;top:0;width:10px}.data-grid .data-grid-th{background-clip:padding-box;color:#fff;padding:1rem;position:relative;vertical-align:middle}.data-grid .data-grid-th._resize-visible .shadow-div{cursor:auto;display:none}.data-grid .data-grid-th._draggable{cursor:grab}.data-grid .data-grid-th._sortable{cursor:pointer;transition:background-color .1s linear;z-index:1}.data-grid .data-grid-th._sortable:focus,.data-grid .data-grid-th._sortable:hover{background-color:#5f564f}.data-grid .data-grid-th._sortable:active{padding-bottom:.9rem;padding-top:1.1rem}.data-grid .data-grid-th.required>span:after{color:#f38a5e;content:'*';margin-left:.3rem}.data-grid .data-grid-checkbox-cell{overflow:hidden;padding:0;vertical-align:top;width:5.2rem}.data-grid .data-grid-checkbox-cell:hover{cursor:default}.data-grid .data-grid-thumbnail-cell{text-align:center;width:7rem}.data-grid .data-grid-thumbnail-cell img{border:1px solid #d6d6d6;width:5rem}.data-grid .data-grid-multicheck-cell{padding:1rem 1rem .9rem;text-align:center;vertical-align:middle}.data-grid .data-grid-onoff-cell{text-align:center;width:12rem}.data-grid .data-grid-actions-cell{padding-left:2rem;padding-right:2rem;text-align:center;width:1%}.data-grid._hidden{display:none}.data-grid._dragging-copy{box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;opacity:.95;position:fixed;top:0;z-index:1000}.data-grid._dragging-copy .data-grid-th{border:1px solid #007bdb;border-bottom:none}.data-grid._dragging-copy .data-grid-th,.data-grid._dragging-copy .data-grid-th._sortable{cursor:grabbing}.data-grid._dragging-copy tr:last-child td{border-bottom:1px solid #007bdb}.data-grid._dragging-copy td{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:rgba(255,251,230,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td,.data-grid._dragging-copy._in-edit .data-grid-editable-row:hover td{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:after,.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{left:0;right:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:only-child{border-left:1px solid #007bdb;border-right:1px solid #007bdb;left:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-select,.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-text{opacity:.5}.data-grid .data-grid-controls-row td{padding-top:1.6rem}.data-grid .data-grid-controls-row td.data-grid-checkbox-cell{padding-top:.6rem}.data-grid .data-grid-controls-row td [class*=admin__control-],.data-grid .data-grid-controls-row td button{margin-top:-1.7rem}.data-grid._in-edit tr:hover td{background-color:#e6e6e6}.data-grid._in-edit ._odd-row.data-grid-editable-row td,.data-grid._in-edit ._odd-row.data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit ._odd-row td,.data-grid._in-edit ._odd-row:hover td{background-color:#dcdcdc}.data-grid._in-edit .data-grid-editable-row-actions td,.data-grid._in-edit .data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid._in-edit td{background-color:#e6e6e6;pointer-events:none}.data-grid._in-edit .data-grid-checkbox-cell{pointer-events:auto}.data-grid._in-edit .data-grid-editable-row{border:.1rem solid #adadad;border-bottom-color:#c2c2c2}.data-grid._in-edit .data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit .data-grid-editable-row td{background-color:#fff;border-bottom-color:#fff;border-left-style:hidden;border-right-style:hidden;border-top-color:#fff;pointer-events:auto;vertical-align:middle}.data-grid._in-edit .data-grid-editable-row td:first-child{border-left-color:#adadad;border-left-style:solid}.data-grid._in-edit .data-grid-editable-row td:first-child:after,.data-grid._in-edit .data-grid-editable-row td:first-child:before{left:0}.data-grid._in-edit .data-grid-editable-row td:last-child{border-right-color:#adadad;border-right-style:solid;left:-.1rem}.data-grid._in-edit .data-grid-editable-row td:last-child:after,.data-grid._in-edit .data-grid-editable-row td:last-child:before{right:0}.data-grid._in-edit .data-grid-editable-row .admin__control-select,.data-grid._in-edit .data-grid-editable-row .admin__control-text{width:100%}.data-grid._in-edit .data-grid-bulk-edit-panel td{vertical-align:bottom}.data-grid .data-grid-editable-row td{border-left-color:#fff;border-left-style:solid;position:relative;z-index:1}.data-grid .data-grid-editable-row td:after{bottom:0;box-shadow:0 5px 5px rgba(0,0,0,.25);content:'';height:.9rem;left:0;margin-top:-1rem;position:absolute;right:0}.data-grid .data-grid-editable-row td:before{background-color:#fff;bottom:0;content:'';height:1rem;left:-10px;position:absolute;right:-10px;z-index:1}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td,.data-grid .data-grid-editable-row.data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:first-child{border-left-color:#fff;border-right-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:last-child{left:0}.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:#fffbe6}.data-grid .data-grid-editable-row-actions{left:50%;margin-left:-12.5rem;margin-top:-2px;position:absolute;text-align:center}.data-grid .data-grid-editable-row-actions td{width:25rem}.data-grid .data-grid-editable-row-actions [class*=action-]{min-width:9rem}.data-grid .data-grid-draggable-row-cell{width:1%}.data-grid .data-grid-draggable-row-cell .draggable-handle{padding:0}.data-grid-th._sortable._ascend,.data-grid-th._sortable._descend{padding-right:2.7rem}.data-grid-th._sortable._ascend:before,.data-grid-th._sortable._descend:before{margin-top:-1em;position:absolute;right:1rem;top:50%}.data-grid-th._sortable._ascend:before{content:'\2193'}.data-grid-th._sortable._descend:before{content:'\2191'}.data-grid-checkbox-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:right}.data-grid-checkbox-cell-inner:hover{cursor:pointer}.data-grid-state-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:center}.data-grid-state-cell-inner>span{display:inline-block;font-style:italic;padding:.6rem 0}.data-grid-row-parent._active>td .data-grid-checkbox-cell-inner:before{content:'\e62b'}.data-grid-row-parent>td .data-grid-checkbox-cell-inner{padding-left:3.7rem;position:relative}.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before{content:'\e628';font-size:1rem;font-weight:700;left:1.35rem;position:absolute;top:1.6rem}.data-grid-th._col-xs{width:1%}.data-grid-info-panel{box-shadow:0 0 5px rgba(0,0,0,.5);margin:2rem .1rem -2rem}.data-grid-info-panel .messages{overflow:hidden}.data-grid-info-panel .messages .message{margin:1rem}.data-grid-info-panel .messages .message:last-child{margin-bottom:1rem}.data-grid-info-panel-actions{padding:1rem;text-align:right}.data-grid-editable-row .admin__field-control{position:relative}.data-grid-editable-row .admin__field-control._error:after{border-color:transparent #ee7d7d transparent transparent;border-style:solid;border-width:0 12px 12px 0;content:'';position:absolute;right:0;top:0}.data-grid-editable-row .admin__field-control._error .admin__control-text{border-color:#ee7d7d}.data-grid-editable-row .admin__field-control._focus:after{display:none}.data-grid-editable-row .admin__field-error{bottom:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin:0 auto 1.5rem;max-width:32rem;position:absolute;right:0}.data-grid-editable-row .admin__field-error:after,.data-grid-editable-row .admin__field-error:before{border-style:solid;content:'';left:50%;position:absolute;top:100%}.data-grid-editable-row .admin__field-error:after{border-color:#fffbbb transparent transparent;border-width:10px 10px 0;margin-left:-10px;z-index:1}.data-grid-editable-row .admin__field-error:before{border-color:#ee7d7d transparent transparent;border-width:11px 12px 0;margin-left:-12px}.data-grid-bulk-edit-panel .admin__field-label-vertical{display:block;font-size:1.2rem;margin-bottom:.5rem;text-align:left}.data-grid-row-changed{cursor:default;display:block;opacity:.5;position:relative;width:100%;z-index:1}.data-grid-row-changed:after{content:'\e631';display:inline-block}.data-grid-row-changed .data-grid-row-changed-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:100%;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;line-height:1.36;margin-bottom:1.5rem;padding:1rem;position:absolute;right:-1rem;text-transform:none;width:27rem;word-break:normal;z-index:2}.data-grid-row-changed._changed{opacity:1;z-index:3}.data-grid-row-changed._changed:hover .data-grid-row-changed-tooltip{display:block}.data-grid-row-changed._changed:hover:before{background:#f1f1f1;border:1px solid #f1f1f1;bottom:100%;box-shadow:4px 4px 3px -1px rgba(0,0,0,.15);content:'';display:block;height:1.6rem;left:50%;margin:0 0 .7rem -.8rem;position:absolute;-ms-transform:rotate(45deg);transform:rotate(45deg);width:1.6rem;z-index:3}.ie9 .data-grid-row-changed._changed:hover:before{display:none}.admin__data-grid-outer-wrap .data-grid-checkbox-cell{overflow:hidden}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner{position:relative}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner:before{bottom:0;content:'';height:500%;left:0;position:absolute;right:0;top:0}.admin__data-grid-wrap-static .data-grid-checkbox-cell:hover{cursor:pointer}.admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:1.1rem 1.8rem .9rem;padding:0}.adminhtml-cms-hierarchy-index .admin__data-grid-wrap-static .data-grid-actions-cell:first-child{padding:0}.adminhtml-export-index .admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:0;padding:1.1rem 1.8rem 1.9rem}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before,.admin__control-file-label:before,.admin__control-multiselect,.admin__control-select,.admin__control-text,.admin__control-textarea,.selectmenu{-webkit-appearance:none;background-color:#fff;border:1px solid #adadad;border-radius:1px;box-shadow:none;color:#303030;font-size:1.4rem;font-weight:400;height:auto;line-height:1.36;padding:.6rem 1rem;transition:border-color .1s linear;vertical-align:baseline;width:auto}.admin__control-addon [class*=admin__control-][class]:hover~[class*=admin__addon-]:last-child:before,.admin__control-multiselect:hover,.admin__control-select:hover,.admin__control-text:hover,.admin__control-textarea:hover,.selectmenu:hover,.selectmenu:hover .selectmenu-toggle:before{border-color:#878787}.admin__control-addon [class*=admin__control-][class]:focus~[class*=admin__addon-]:last-child:before,.admin__control-file:active+.admin__control-file-label:before,.admin__control-file:focus+.admin__control-file-label:before,.admin__control-multiselect:focus,.admin__control-select:focus,.admin__control-text:focus,.admin__control-textarea:focus,.selectmenu._focus,.selectmenu._focus .selectmenu-toggle:before{border-color:#007bdb;box-shadow:none;outline:0}.admin__control-addon [class*=admin__control-][class][disabled]~[class*=admin__addon-]:last-child:before,.admin__control-file[disabled]+.admin__control-file-label:before,.admin__control-multiselect[disabled],.admin__control-select[disabled],.admin__control-text[disabled],.admin__control-textarea[disabled]{background-color:#e9e9e9;border-color:#adadad;color:#303030;cursor:not-allowed;opacity:.5}.admin__field-row[class]>.admin__field-control,.admin__fieldset>.admin__field.admin__field-wide[class]>.admin__field-control{clear:left;float:none;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label{display:block;line-height:1.4rem;margin-bottom:.86rem;margin-top:-.14rem;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label:before,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label:before{display:none}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span{padding-left:1.5rem}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span:after{left:0;margin-left:30px}.admin__legend{font-size:1.8rem;font-weight:600;margin-bottom:3rem}.admin__control-checkbox,.admin__control-radio{cursor:pointer;opacity:.01;overflow:hidden;position:absolute;vertical-align:top}.admin__control-checkbox:after,.admin__control-radio:after{display:none}.admin__control-checkbox+label,.admin__control-radio+label{cursor:pointer;display:inline-block}.admin__control-checkbox+label:before,.admin__control-radio+label:before{background-color:#fff;border:1px solid #adadad;color:transparent;float:left;height:1.6rem;text-align:center;vertical-align:top;width:1.6rem}.admin__control-checkbox+.admin__field-label,.admin__control-radio+.admin__field-label{padding-left:2.6rem}.admin__control-checkbox+.admin__field-label:before,.admin__control-radio+.admin__field-label:before{margin:1px 1rem 0 -2.6rem}.admin__control-checkbox:checked+label:before,.admin__control-radio:checked+label:before{color:#514943}.admin__control-checkbox.disabled+label,.admin__control-checkbox[disabled]+label,.admin__control-radio.disabled+label,.admin__control-radio[disabled]+label{color:#303030;cursor:default;opacity:.5}.admin__control-checkbox.disabled+label:before,.admin__control-checkbox[disabled]+label:before,.admin__control-radio.disabled+label:before,.admin__control-radio[disabled]+label:before{background-color:#e9e9e9;border-color:#adadad;cursor:default}._keyfocus .admin__control-checkbox:not(.disabled):focus+label:before,._keyfocus .admin__control-checkbox:not([disabled]):focus+label:before,._keyfocus .admin__control-radio:not(.disabled):focus+label:before,._keyfocus .admin__control-radio:not([disabled]):focus+label:before{border-color:#007bdb}.admin__control-checkbox:not(.disabled):hover+label:before,.admin__control-checkbox:not([disabled]):hover+label:before,.admin__control-radio:not(.disabled):hover+label:before,.admin__control-radio:not([disabled]):hover+label:before{border-color:#878787}.admin__control-radio+label:before{border-radius:1.6rem;content:'';transition:border-color .1s linear,color .1s ease-in}.admin__control-radio.admin__control-radio+label:before{line-height:140%}.admin__control-radio:checked+label{position:relative}.admin__control-radio:checked+label:after{background-color:#514943;border-radius:50%;content:'';height:10px;left:3px;position:absolute;top:4px;width:10px}.admin__control-radio:checked:not(.disabled):hover,.admin__control-radio:checked:not(.disabled):hover+label,.admin__control-radio:checked:not([disabled]):hover,.admin__control-radio:checked:not([disabled]):hover+label{cursor:default}.admin__control-radio:checked:not(.disabled):hover+label:before,.admin__control-radio:checked:not([disabled]):hover+label:before{border-color:#adadad}.admin__control-checkbox+label:before{border-radius:1px;content:'';font-size:0;transition:font-size .1s ease-out,color .1s ease-out,border-color .1s linear}.admin__control-checkbox:checked+label:before{content:'\e62d';font-size:1.1rem;line-height:125%}.admin__control-checkbox:not(:checked)._indeterminate+label:before,.admin__control-checkbox:not(:checked):indeterminate+label:before{color:#514943;content:'-';font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700}input[type=checkbox].admin__control-checkbox,input[type=radio].admin__control-checkbox{margin:0;position:absolute}.admin__control-text{min-width:4rem}.admin__control-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#adadad,#adadad);background-position:calc(100% - 12px) -34px,100%,calc(100% - 3.2rem) 0;background-size:auto,3.2rem 100%,1px 100%;background-repeat:no-repeat;max-width:100%;min-width:8.5rem;padding-bottom:.5rem;padding-right:4.4rem;padding-top:.5rem;transition:border-color .1s linear}.admin__control-select:hover{border-color:#878787;cursor:pointer}.admin__control-select:focus{background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#007bdb,#007bdb);background-position:calc(100% - 12px) 13px,100%,calc(100% - 3.2rem) 0;border-color:#007bdb}.admin__control-select::-ms-expand{display:none}.ie9 .admin__control-select{background-image:none;padding-right:1rem}option:empty{display:none}.admin__control-multiselect{height:auto;max-width:100%;min-width:15rem;overflow:auto;padding:0;resize:both}.admin__control-multiselect optgroup,.admin__control-multiselect option{padding:.5rem 1rem}.admin__control-file-wrapper{display:inline-block;padding:.5rem 1rem;position:relative;z-index:1}.admin__control-file-label:before{content:'';left:0;position:absolute;top:0;width:100%;z-index:0}.admin__control-file{background:0 0;border:0;padding-top:.7rem;position:relative;width:auto;z-index:1}.admin__control-support-text{border:1px solid transparent;display:inline-block;font-size:1.4rem;line-height:1.36;padding-bottom:.6rem;padding-top:.6rem}.admin__control-support-text+[class*=admin__control-],[class*=admin__control-]+.admin__control-support-text{margin-left:.7rem}.admin__control-service{float:left;margin:.8rem 0 0 3rem}.admin__control-textarea{height:8.48rem;line-height:1.18;padding-top:.8rem;resize:vertical}.admin__control-addon{-ms-flex-direction:row;flex-direction:row;display:inline-flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;position:relative;width:100%;z-index:1}.admin__control-addon>[class*=admin__addon-],.admin__control-addon>[class*=admin__control-]{-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;position:relative;z-index:1}.admin__control-addon .admin__control-select{width:auto}.admin__control-addon .admin__control-text{margin:.1rem;padding:.5rem .9rem;width:100%}.admin__control-addon [class*=admin__control-][class]{appearence:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-order:1;order:1;-ms-flex-negative:1;flex-shrink:1;background-color:transparent;border-color:transparent;box-shadow:none;vertical-align:top}.admin__control-addon [class*=admin__control-][class]+[class*=admin__control-]{border-left-color:#adadad}.admin__control-addon [class*=admin__control-][class] :focus{box-shadow:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child{padding-left:1rem;position:static!important;z-index:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child>*{position:relative;vertical-align:top;z-index:1}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:empty{padding:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before{bottom:0;box-sizing:border-box;content:'';left:0;position:absolute;top:0;width:100%;z-index:-1}.admin__addon-prefix,.admin__addon-suffix{border:0;box-sizing:border-box;color:#858585;display:inline-block;font-size:1.4rem;font-weight:400;height:3.2rem;line-height:3.2rem;padding:0}.admin__addon-suffix{-ms-flex-order:3;order:3}.admin__addon-suffix:last-child{padding-right:1rem}.admin__addon-prefix{-ms-flex-order:0;order:0}.ie9 .admin__control-addon:after{clear:both;content:'';display:block;height:0;overflow:hidden}.ie9 .admin__addon{min-width:0;overflow:hidden;text-align:right;white-space:nowrap;width:auto}.ie9 .admin__addon [class*=admin__control-]{display:inline}.ie9 .admin__addon-prefix{float:left}.ie9 .admin__addon-suffix{float:right}.admin__control-collapsible{width:100%}.admin__control-collapsible ._dragged .admin__collapsible-block-wrapper .admin__collapsible-title{background:#d0d0d0}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before,.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{background:#008bdb;content:'';display:block;height:3px;left:0;position:absolute;right:0}.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{top:-3px}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before{bottom:-3px}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper{border:0;margin:0;position:relative}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper .fieldset-wrapper-title{background:#f8f8f8;border:2px solid #ccc}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title{font-size:1.4rem;font-weight:400;line-height:1;padding:1.6rem 4rem 1.6rem 3.8rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title:before{left:1rem;right:auto;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding:0;position:absolute;right:1rem;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before{content:'\e630';font-size:2rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete>span{display:none}.admin__control-collapsible .admin__collapsible-content{background-color:#fff;margin-bottom:1rem}.admin__control-collapsible .admin__collapsible-content>.fieldset-wrapper{border:1px solid #ccc;margin-top:-1px;padding:1rem}.admin__control-collapsible .admin__collapsible-content .admin__fieldset{padding:0}.admin__control-collapsible .admin__collapsible-content .admin__field:last-child{margin-bottom:0}.admin__control-table-wrapper{max-width:100%;overflow-x:auto;overflow-y:hidden}.admin__control-table{width:100%}.admin__control-table thead{background-color:transparent}.admin__control-table tbody td{vertical-align:top}.admin__control-table tfoot th{padding-bottom:1.3rem}.admin__control-table tfoot th.validation{padding-bottom:0;padding-top:0}.admin__control-table tfoot td{border-top:1px solid #fff}.admin__control-table tfoot .admin__control-table-pagination{float:right;padding-bottom:0}.admin__control-table tfoot .action-previous{margin-right:.5rem}.admin__control-table tfoot .action-next{margin-left:.9rem}.admin__control-table tr:last-child td{border-bottom:none}.admin__control-table tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.admin__control-table tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.admin__control-table tr._dragged td,.admin__control-table tr._dragged th{background:#d0d0d0}.admin__control-table td,.admin__control-table th{background-color:#efefef;border:0;border-bottom:1px solid #fff;padding:1.3rem 1rem 1.3rem 0;text-align:left;vertical-align:top}.admin__control-table td:first-child,.admin__control-table th:first-child{padding-left:1rem}.admin__control-table td>.admin__control-select,.admin__control-table td>.admin__control-text,.admin__control-table th>.admin__control-select,.admin__control-table th>.admin__control-text{width:100%}.admin__control-table td._hidden,.admin__control-table th._hidden{display:none}.admin__control-table td._fit,.admin__control-table th._fit{width:1px}.admin__control-table th{color:#303030;font-size:1.4rem;font-weight:600;vertical-align:bottom}.admin__control-table th._required span:after{color:#eb5202;content:'*'}.admin__control-table .control-table-actions-th{white-space:nowrap}.admin__control-table .control-table-actions-cell{padding-top:1.8rem;text-align:center;width:1%}.admin__control-table .control-table-options-th{text-align:center;width:10rem}.admin__control-table .control-table-options-cell{text-align:center}.admin__control-table .control-table-text{line-height:3.2rem}.admin__control-table .col-draggable{padding-top:2.2rem;width:1%}.admin__control-table .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.admin__control-table .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-table .action-delete:before{content:'\e630';font-size:2rem}.admin__control-table .action-delete>span{display:none}.admin__control-table .draggable-handle{padding:0}.admin__control-table._dragged{outline:#007bdb solid 1px}.admin__control-table-action{background-color:#efefef;border-top:1px solid #fff;padding:1.3rem 1rem}.admin__dynamic-rows._dragged{opacity:.95;position:absolute;z-index:999}.admin__dynamic-rows.admin__control-table .admin__control-fields>.admin__field{border:0;padding:0}.admin__dynamic-rows td>.admin__field{border:0;margin:0;padding:0}.admin__control-table-pagination{padding-bottom:1rem}.admin__control-table-pagination .admin__data-grid-pager{float:right}.admin__field-tooltip{display:inline-block;margin-top:.5rem;max-width:45px;overflow:visible;vertical-align:top;width:0}.admin__field-tooltip:hover{position:relative;z-index:500}.admin__field-option .admin__field-tooltip{margin-top:.5rem}.admin__field-tooltip .admin__field-tooltip-action{margin-left:2rem;position:relative;z-index:2;display:inline-block;text-decoration:none}.admin__field-tooltip .admin__field-tooltip-action:before{-webkit-font-smoothing:antialiased;font-size:2.2rem;line-height:1;color:#514943;content:'\e633';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.admin__field-tooltip .admin__control-text:focus+.admin__field-tooltip-content,.admin__field-tooltip:hover .admin__field-tooltip-content{display:block}.admin__field-tooltip .admin__field-tooltip-content{bottom:3.8rem;display:none;right:-2.3rem}.admin__field-tooltip .admin__field-tooltip-content:after,.admin__field-tooltip .admin__field-tooltip-content:before{border:1.6rem solid transparent;height:0;width:0;border-top-color:#afadac;content:'';display:block;position:absolute;right:2rem;top:100%;z-index:3}.admin__field-tooltip .admin__field-tooltip-content:after{border-top-color:#fffbbb;margin-top:-1px;z-index:4}.abs-admin__field-tooltip-content,.admin__field-tooltip .admin__field-tooltip-content{box-shadow:0 2px 8px 0 rgba(0,0,0,.3);background:#fffbbb;border:1px solid #afadac;border-radius:1px;padding:1.5rem 2.5rem;position:absolute;width:32rem;z-index:1}.admin__field-fallback-reset{font-size:1.25rem;white-space:nowrap;width:30px}.admin__field-fallback-reset>span{margin-left:.5rem;position:relative}.admin__field-fallback-reset:active{-ms-transform:scale(0.98);transform:scale(0.98)}.admin__field-fallback-reset:before{transition:color .1s linear;content:'\e642';font-size:1.3rem;margin-left:.5rem}.admin__field-fallback-reset:hover{cursor:pointer;text-decoration:none}.admin__field-fallback-reset:focus{background:0 0}.abs-field-size-x-small,.abs-field-sizes.admin__field-x-small>.admin__field-control,.admin__field.admin__field-x-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-x-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-x-small>.admin__field-control{width:8rem}.abs-field-size-small,.abs-field-sizes.admin__field-small>.admin__field-control,.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control,.admin__field.admin__field-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-small>.admin__field-control{width:15rem}.abs-field-size-medium,.abs-field-sizes.admin__field-medium>.admin__field-control,.admin__field.admin__field-medium>.admin__field-control,.admin__fieldset>.admin__field.admin__field-medium>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-medium>.admin__field-control{width:34rem}.abs-field-size-large,.abs-field-sizes.admin__field-large>.admin__field-control,.admin__field.admin__field-large>.admin__field-control,.admin__fieldset>.admin__field.admin__field-large>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-large>.admin__field-control{width:64rem}.abs-field-no-label,.admin__field-group-additional,.admin__field-no-label,.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-control{margin-left:calc((100%) * .25 + 30px)}.admin__fieldset{border:0;margin:0;min-width:0;padding:0}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title{padding-left:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title strong{font-size:1.7rem;font-weight:600}.admin__fieldset .fieldset-wrapper.admin__fieldset-section .admin__fieldset-wrapper-content>.admin__fieldset{padding-top:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section:last-child .admin__fieldset-wrapper-content>.admin__fieldset{padding-bottom:0}.admin__fieldset>.admin__field{border:0;margin:0 0 0 -30px;padding:0}.admin__fieldset>.admin__field:after{clear:both;content:'';display:table}.admin__fieldset>.admin__field>.admin__field-control{width:calc((100%) * .5 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-label{display:none}.admin__fieldset>.admin__field+.admin__field._empty._no-header{margin-top:-3rem}.admin__fieldset-product-websites{position:relative;z-index:300}.admin__fieldset-note{margin-bottom:2rem}.admin__form-field{border:0;margin:0;padding:0}.admin__field-control .admin__control-text,.admin__field-control .admin__control-textarea,.admin__form-field-control .admin__control-text,.admin__form-field-control .admin__control-textarea{width:100%}.admin__field-label{color:#303030;cursor:pointer;margin:0;text-align:right}.admin__field-label+br{display:none}.admin__field:not(.admin__field-option)>.admin__field-label{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:3.2rem;padding:0;white-space:nowrap}.admin__field:not(.admin__field-option)>.admin__field-label:before{opacity:0;visibility:hidden;content:'.';margin-left:-7px;overflow:hidden}.admin__field:not(.admin__field-option)>.admin__field-label span{display:inline-block;line-height:1.2;vertical-align:middle;white-space:normal}.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]{position:relative}._required>.admin__field-label>span:after,.required>.admin__field-label>span:after{color:#eb5202;content:'*';display:inline-block;font-size:1.6rem;font-weight:500;line-height:1;margin-left:10px;margin-top:.2rem;position:absolute;z-index:1}._disabled>.admin__field-label{color:#999;cursor:default}.admin__field{margin-bottom:0}.admin__field+.admin__field{margin-top:1.5rem}.admin__field:not(.admin__field-option)~.admin__field-option{margin-top:.5rem}.admin__field.admin__field-option~.admin__field-option{margin-top:.9rem}.admin__field~.admin__field-option:last-child{margin-bottom:.8rem}.admin__fieldset>.admin__field{margin-bottom:3rem;position:relative}.admin__field legend.admin__field-label{opacity:0}.admin__field[data-config-scope]:before{color:gray;content:attr(data-config-scope);display:inline-block;font-size:1.2rem;left:calc((100%) * .75 - 30px);line-height:3.2rem;margin-left:60px;position:absolute;width:calc((100%) * .25 - 30px)}.admin__field-control .admin__field[data-config-scope]:nth-child(n+2):before{content:''}.admin__field._error .admin__field-control [class*=admin__addon-]:before,.admin__field._error .admin__field-control [class*=admin__control-] [class*=admin__addon-]:before,.admin__field._error .admin__field-control>[class*=admin__control-]{border-color:#e22626}.admin__field._disabled,.admin__field._disabled:hover{box-shadow:inherit;cursor:inherit;opacity:1;outline:inherit}.admin__field._hidden{display:none}.admin__field-control+.admin__field-control{margin-top:1.5rem}.admin__field-control._with-tooltip>.admin__control-addon,.admin__field-control._with-tooltip>.admin__control-select,.admin__field-control._with-tooltip>.admin__control-text,.admin__field-control._with-tooltip>.admin__control-textarea,.admin__field-control._with-tooltip>.admin__field-option{max-width:calc(100% - 45px - 4px)}.admin__field-control._with-tooltip .admin__field-tooltip{width:auto}.admin__field-control._with-tooltip .admin__field-option{display:inline-block}.admin__field-control._with-reset>.admin__control-addon,.admin__field-control._with-reset>.admin__control-text,.admin__field-control._with-reset>.admin__control-textarea{width:calc(100% - 30px - .5rem - 4px)}.admin__field-control._with-reset .admin__field-fallback-reset{margin-left:.5rem;margin-top:1rem;vertical-align:top}.admin__field-control._with-reset._with-tooltip>.admin__control-addon,.admin__field-control._with-reset._with-tooltip>.admin__control-text,.admin__field-control._with-reset._with-tooltip>.admin__control-textarea{width:calc(100% - 30px - .5rem - 45px - 8px)}.admin__fieldset>.admin__field-collapsible{margin-bottom:0}.admin__fieldset>.admin__field-collapsible .admin__field-control{border-top:1px solid #ccc;display:block;font-size:1.7rem;font-weight:700;padding:1.7rem 0;width:calc(97%)}.admin__fieldset>.admin__field-collapsible .admin__field-option{padding-top:0}.admin__field-collapsible+div{margin-top:2.5rem}.admin__field-collapsible .admin__control-radio+label:before{height:1.8rem;width:1.8rem}.admin__field-collapsible .admin__control-radio:checked+label:after{left:4px;top:5px}.admin__field-error{background:#fffbbb;border:1px solid #ee7d7d;box-sizing:border-box;color:#555;display:block;font-size:1.2rem;font-weight:400;line-height:1.2;margin:.2rem 0 0;padding:.8rem 1rem .9rem}.admin__field-note{color:#303030;font-size:1.2rem;margin:10px 0 0;padding:0}.admin__additional-info{padding-top:1rem}.admin__field-option{padding-top:.7rem}.admin__field-option .admin__field-label{text-align:left}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2),.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1){display:inline-block}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option{display:inline-block;margin-left:41px;margin-top:0}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option:before,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option:before{background:#cacaca;content:'';display:inline-block;height:20px;margin-left:-20px;position:absolute;width:1px}.admin__field-value{display:inline-block;padding-top:.7rem}.admin__field-service{padding-top:1rem}.admin__control-fields>.admin__field:first-child,[class*=admin__control-grouped]>.admin__field:first-child{position:static}.admin__control-fields>.admin__field:first-child>.admin__field-label,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px;background:#fff;cursor:pointer;left:0;position:absolute;top:0}.admin__control-fields>.admin__field:first-child>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label span:before{display:block}.admin__control-fields>.admin__field._disabled>.admin__field-label,[class*=admin__control-grouped]>.admin__field._disabled>.admin__field-label{cursor:default}.admin__control-fields>.admin__field>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field>.admin__field-label span:before{display:none}.admin__control-fields .admin__field-label~.admin__field-control{width:100%}.admin__control-fields .admin__field-option{padding-top:0}[class*=admin__control-grouped]{box-sizing:border-box;display:table;width:100%}[class*=admin__control-grouped]>.admin__field{display:table-cell;vertical-align:top}[class*=admin__control-grouped]>.admin__field>.admin__field-control{float:none;width:100%}[class*=admin__control-grouped]>.admin__field.admin__field-default,[class*=admin__control-grouped]>.admin__field.admin__field-large,[class*=admin__control-grouped]>.admin__field.admin__field-medium,[class*=admin__control-grouped]>.admin__field.admin__field-small,[class*=admin__control-grouped]>.admin__field.admin__field-x-small{width:1px}[class*=admin__control-grouped]>.admin__field.admin__field-default+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-large+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-medium+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-small+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-x-small+.admin__field:last-child{width:auto}[class*=admin__control-grouped]>.admin__field:nth-child(n+2){padding-left:20px}.admin__control-group-equal{table-layout:fixed}.admin__control-group-equal>.admin__field{width:50%}.admin__field-control-group{margin-top:.8rem}.admin__field-control-group>.admin__field{padding:0}.admin__control-grouped-date>.admin__field-date{white-space:nowrap;width:1px}.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control{float:left;position:relative}.admin__control-grouped-date>.admin__field-date+.admin__field:last-child{width:auto}.admin__control-grouped-date>.admin__field-date+.admin__field-date>.admin__field-label{float:left;padding-right:20px}.admin__control-grouped-date .ui-datepicker-trigger{left:100%;top:0}.admin__field-group-columns.admin__field-control.admin__control-grouped{width:calc((100%) * 1 - 30px);float:left;margin-left:30px}.admin__field-group-columns>.admin__field:first-child>.admin__field-label{float:none;margin:0;opacity:1;position:static;text-align:left}.admin__field-group-columns .admin__control-select{width:100%}.admin__field-group-additional{clear:both}.admin__field-group-additional .action-advanced{margin-top:1rem}.admin__field-group-additional .action-secondary{width:100%}.admin__field-group-show-label{white-space:nowrap}.admin__field-group-show-label>.admin__field-control,.admin__field-group-show-label>.admin__field-label{display:inline-block;vertical-align:top}.admin__field-group-show-label>.admin__field-label{margin-right:20px}.admin__field-complex{margin:1rem 0 3rem;padding-left:1rem}.admin__field:not(._hidden)+.admin__field-complex{margin-top:3rem}.admin__field-complex .admin__field-complex-title{clear:both;color:#303030;font-size:1.7rem;font-weight:600;letter-spacing:.025em;margin-bottom:1rem}.admin__field-complex .admin__field-complex-elements{float:right;max-width:40%}.admin__field-complex .admin__field-complex-elements button{margin-left:1rem}.admin__field-complex .admin__field-complex-content{max-width:60%;overflow:hidden}.admin__field-complex .admin__field-complex-text{margin-left:-1rem}.admin__field-complex+.admin__field._empty._no-header{margin-top:-3rem}.admin__legend{float:left;position:static;width:100%}.admin__legend+br{clear:left;display:block;height:0;overflow:hidden}.message{margin-bottom:3rem}.message-icon-top:before{margin-top:0;top:1.8rem}.nav{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;display:none;margin-bottom:3rem;padding:2.2rem 1.5rem 0 0}.nav .btn-group,.nav-bar-outer-actions{float:right;margin-bottom:1.7rem}.nav .btn-group .btn-wrap,.nav-bar-outer-actions .btn-wrap{float:right;margin-left:.5rem;margin-right:.5rem}.nav .btn-group .btn-wrap .btn,.nav-bar-outer-actions .btn-wrap .btn{padding-left:.5rem;padding-right:.5rem}.nav-bar-outer-actions{margin-top:-10.6rem;padding-right:1.5rem}.btn-wrap-try-again{width:9.5rem}.btn-wrap-next,.btn-wrap-prev{width:8.5rem}.nav-bar{counter-reset:i;float:left;margin:0 1rem 1.7rem 0;padding:0;position:relative;white-space:nowrap}.nav-bar:before{background-color:#d4d4d4;background-repeat:repeat-x;background-image:linear-gradient(to bottom,#d1d1d1 0,#d4d4d4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#d1d1d1', endColorstr='#d4d4d4', GradientType=0);border-bottom:1px solid #d9d9d9;border-top:1px solid #bfbfbf;content:'';height:1rem;left:5.15rem;position:absolute;right:5.15rem;top:.7rem}.nav-bar>li{display:inline-block;font-size:0;position:relative;vertical-align:top;width:10.3rem}.nav-bar>li:first-child:after{display:none}.nav-bar>li:after{background-color:#514943;content:'';height:.5rem;left:calc(-50% + .25rem);position:absolute;right:calc(50% + .7rem);top:.9rem}.nav-bar>li.disabled:before,.nav-bar>li.ui-state-disabled:before{bottom:0;content:'';left:0;position:absolute;right:0;top:0;z-index:1}.nav-bar>li.active~li:after,.nav-bar>li.ui-state-active~li:after{display:none}.nav-bar>li.active~li a:after,.nav-bar>li.ui-state-active~li a:after{background-color:transparent;border-color:transparent;color:#a6a6a6}.nav-bar>li.active a,.nav-bar>li.ui-state-active a{color:#000}.nav-bar>li.active a:hover,.nav-bar>li.ui-state-active a:hover{cursor:default}.nav-bar>li.active a:after,.nav-bar>li.ui-state-active a:after{background-color:#fff;content:''}.nav-bar a{color:#514943;display:block;font-size:1.2rem;font-weight:600;line-height:1.2;overflow:hidden;padding:3rem .5em 0;position:relative;text-align:center;text-overflow:ellipsis}.nav-bar a:hover{text-decoration:none}.nav-bar a:after{background-color:#514943;border:.4rem solid #514943;border-radius:100%;color:#fff;content:counter(i);counter-increment:i;height:1.5rem;left:50%;line-height:.6;margin-left:-.8rem;position:absolute;right:auto;text-align:center;top:.4rem;width:1.5rem}.nav-bar a:before{background-color:#d6d6d6;border:1px solid transparent;border-bottom-color:#d9d9d9;border-radius:100%;border-top-color:#bfbfbf;content:'';height:2.3rem;left:50%;line-height:1;margin-left:-1.2rem;position:absolute;top:0;width:2.3rem}.tooltip{display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.19rem;font-weight:400;line-height:1.4;opacity:0;position:absolute;visibility:visible;z-index:10}.tooltip.in{opacity:.9}.tooltip.top{margin-top:-4px;padding:8px 0}.tooltip.right{margin-left:4px;padding:0 8px}.tooltip.bottom{margin-top:4px;padding:8px 0}.tooltip.left{margin-left:-4px;padding:0 8px}.tooltip p:last-child{margin-bottom:0}.tooltip-inner{background-color:#fff;border:1px solid #adadad;border-radius:0;box-shadow:1px 1px 1px #ccc;color:#41362f;max-width:31rem;padding:.5em 1em;text-decoration:none}.tooltip-arrow,.tooltip-arrow:after{border:solid transparent;height:0;position:absolute;width:0}.tooltip-arrow:after{content:'';position:absolute}.tooltip.top .tooltip-arrow,.tooltip.top .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:50%;margin-left:-8px}.tooltip.top-left .tooltip-arrow,.tooltip.top-left .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;margin-bottom:-8px;right:8px}.tooltip.top-right .tooltip-arrow,.tooltip.top-right .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:8px;margin-bottom:-8px}.tooltip.right .tooltip-arrow,.tooltip.right .tooltip-arrow:after{border-right-color:#949494;border-width:8px 8px 8px 0;left:1px;margin-top:-8px;top:50%}.tooltip.right .tooltip-arrow:after{border-right-color:#fff;border-width:6px 7px 6px 0;margin-left:0;margin-top:-6px}.tooltip.left .tooltip-arrow,.tooltip.left .tooltip-arrow:after{border-left-color:#949494;border-width:8px 0 8px 8px;margin-top:-8px;right:0;top:50%}.tooltip.bottom .tooltip-arrow,.tooltip.bottom .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:50%;margin-left:-8px;top:0}.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;margin-top:-8px;right:8px;top:0}.tooltip.bottom-right .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:8px;margin-top:-8px;top:0}.password-strength{display:block;margin:0 -.3rem 1em;white-space:nowrap}.password-strength.password-strength-too-short .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child+.password-strength-item{background-color:#e22626}.password-strength.password-strength-fair .password-strength-item:first-child,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item+.password-strength-item{background-color:#ef672f}.password-strength.password-strength-good .password-strength-item:first-child,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item+.password-strength-item,.password-strength.password-strength-strong .password-strength-item{background-color:#79a22e}.password-strength .password-strength-item{background-color:#ccc;display:inline-block;font-size:0;height:1.4rem;margin-right:.3rem;width:calc(20% - .6rem)}@keyframes progress-bar-stripes{from{background-position:4rem 0}to{background-position:0 0}}.progress{background-color:#fafafa;border:1px solid #ccc;clear:left;height:3rem;margin-bottom:3rem;overflow:hidden}.progress-bar{background-color:#79a22e;color:#fff;float:left;font-size:1.19rem;height:100%;line-height:3rem;text-align:center;transition:width .6s ease;width:0}.progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.progress-bar-text-description{margin-bottom:1.6rem}.progress-bar-text-progress{text-align:right}.page-columns .page-inner-sidebar{margin:0 0 3rem}.page-header{margin-bottom:2.7rem;padding-bottom:2rem;position:relative}.page-header:before{border-bottom:1px solid #e3e3e3;bottom:0;content:'';display:block;height:1px;left:3rem;position:absolute;right:3rem}.container .page-header:before{content:normal}.page-header .message{margin-bottom:1.8rem}.page-header .message+.message{margin-top:-1.5rem}.page-header .admin__action-dropdown,.page-header .search-global-input{transition:none}.container .page-header{margin-bottom:0}.page-title-wrapper{margin-top:1.1rem}.container .page-title-wrapper{background:url(../../pub/images/logo.svg) no-repeat;min-height:41px;padding:4px 0 0 45px}.admin__menu .level-0:first-child>a{margin-top:1.6rem}.admin__menu .level-0:first-child>a:after{top:-1.6rem}.admin__menu .level-0:first-child._active>a:after{display:block}.admin__menu .level-0>a{padding-bottom:1.3rem;padding-top:1.3rem}.admin__menu .level-0>a:before{margin-bottom:.7rem}.admin__menu .item-home>a:before{content:'\e611';font-size:2.3rem;padding-top:-.1rem}.admin__menu .item-component>a:before{content:'\e612'}.admin__menu .item-extension>a:before{content:'\e647'}.admin__menu .item-upgrade>a:before{content:'\e614'}.admin__menu .item-system-config>a:before{content:'\e610'}.admin__menu .item-tools>a:before{content:'\e613'}.modal-sub-title{font-size:1.7rem;font-weight:600}.modal-connect-signin .modal-inner-wrap{max-width:80rem}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{-webkit-overflow-scrolling:touch;bottom:0;box-sizing:border-box;left:0;overflow:auto;position:fixed;right:0;top:0;z-index:999}.ngdialog *,.ngdialog:after,.ngdialog:before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation *{animation:none!important}.ngdialog.ngdialog-closing .ngdialog-content,.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-animation:ngdialog-fadeout .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadeout .5s}.ngdialog-overlay{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s;background:rgba(0,0,0,.4);bottom:0;left:0;position:fixed;right:0;top:0}.ngdialog-content{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s}body.ngdialog-open{overflow:hidden}.component-indicator{border-radius:50%;cursor:help;display:inline-block;height:16px;text-align:center;vertical-align:middle;width:16px}.component-indicator::after,.component-indicator::before{background:#fff;display:block;opacity:0;position:absolute;transition:opacity .2s linear .1s;visibility:hidden}.component-indicator::before{border:1px solid #adadad;border-radius:1px;box-shadow:0 0 2px rgba(0,0,0,.4);content:attr(data-label);font-size:1.2rem;margin:30px 0 0 -10px;min-width:50px;padding:4px 5px}.component-indicator::after{border-color:#999;border-style:solid;border-width:1px 0 0 1px;box-shadow:-1px -1px 1px rgba(0,0,0,.1);content:'';height:10px;margin:9px 0 0 5px;-ms-transform:rotate(45deg);transform:rotate(45deg);width:10px}.component-indicator:hover::after,.component-indicator:hover::before{opacity:1;transition:opacity .2s linear;visibility:visible}.component-indicator span{display:block;height:16px;overflow:hidden;width:16px}.component-indicator span:before{content:'';display:block;font-family:Icons;font-size:16px;height:100%;line-height:16px;width:100%}.component-indicator._on{background:#79a22e}.component-indicator._off{background:#e22626}.component-indicator._off span:before{background:#fff;height:4px;margin:8px auto 20px;width:12px}.component-indicator._info{background:0 0}.component-indicator._info span{width:21px}.component-indicator._info span:before{color:#008bdb;content:'\e648';font-family:Icons;font-size:16px}.component-indicator._tooltip{background:0 0;margin:0 0 8px 5px}.component-indicator._tooltip a{width:21px}.component-indicator._tooltip a:hover{text-decoration:none}.component-indicator._tooltip a:before{color:#514943;content:'\e633';font-family:Icons;font-size:16px}.col-manager-item-name .data-grid-data{padding-left:5px}.col-manager-item-name .ng-hide+.data-grid-data{padding-left:24px}.col-manager-item-name ._hide-dependencies,.col-manager-item-name ._show-dependencies{cursor:pointer;padding-left:24px;position:relative}.col-manager-item-name ._hide-dependencies:before,.col-manager-item-name ._show-dependencies:before{display:block;font-family:Icons;font-size:12px;left:0;position:absolute;top:1px}.col-manager-item-name ._show-dependencies:before{content:'\e62b'}.col-manager-item-name ._hide-dependencies:before{content:'\e628'}.col-manager-item-name ._no-dependencies{padding-left:24px}.product-modules-block{font-size:1.2rem;padding:15px 0 0}.col-manager-item-name .product-modules-block{padding-left:1rem}.product-modules-descriprion,.product-modules-title{font-weight:700;margin:0 0 7px}.product-modules-list{font-size:1.1rem;list-style:none;margin:0}.col-manager-item-name .product-modules-list{margin-left:15px}.col-manager-item-name .product-modules-list li{padding:0 0 0 15px;position:relative}.product-modules-list li{margin:0 0 .5rem}.product-modules-list .component-indicator{height:10px;left:0;position:absolute;top:3px;width:10px}.module-summary{white-space:nowrap}.module-summary-title{font-size:2.1rem;margin-right:1rem}.app-updater .nav{display:block;margin-bottom:3.1rem;margin-top:-2.8rem}.app-updater .nav-bar-outer-actions{margin-top:1rem;padding-right:0}.app-updater .nav-bar-outer-actions .btn-wrap-cancel{margin-right:2.6rem}.main{padding-bottom:2rem;padding-top:3rem}.menu-wrapper .logo-static{pointer-events:none}.header{display:none}.header .logo{float:left;height:4.1rem;width:3.5rem}.header-title{font-size:2.8rem;letter-spacing:.02em;line-height:1.4;margin:2.5rem 0 3.5rem 5rem}.page-title{margin-bottom:1rem}.page-sub-title{font-size:2rem}.accent-box{margin-bottom:2rem}.accent-box .btn-prime{margin-top:1.5rem}.spinner.side{float:left;font-size:2.4rem;margin-left:2rem;margin-top:-5px}.page-landing{margin:7.6% auto 0;max-width:44rem;text-align:center}.page-landing .logo{height:5.6rem;margin-bottom:2rem;width:19.2rem}.page-landing .text-version{margin-bottom:3rem}.page-landing .text-welcome{margin-bottom:6.5rem}.page-landing .text-terms{margin-bottom:2.5rem;text-align:center}.page-landing .btn-submit,.page-license .license-text{margin-bottom:2rem}.page-license .page-license-footer{text-align:right}.readiness-check-item{margin-bottom:4rem;min-height:2.5rem}.readiness-check-item .spinner{float:left;font-size:2.5rem;margin:-.4rem 0 0 1.7rem}.readiness-check-title{font-size:1.4rem;font-weight:700;margin-bottom:.1rem;margin-left:5.7rem}.readiness-check-content{margin-left:5.7rem;margin-right:22rem;position:relative}.readiness-check-content .readiness-check-title{margin-left:0}.readiness-check-content .list{margin-top:-.3rem}.readiness-check-side{left:100%;padding-left:2.4rem;position:absolute;top:0;width:22rem}.readiness-check-side .side-title{margin-bottom:0}.readiness-check-icon{float:left;margin-left:1.7rem;margin-top:.3rem}.extensions-information{margin-bottom:5rem}.extensions-information h3{font-size:1.4rem;margin-bottom:1.3rem}.extensions-information .message{margin-bottom:2.5rem}.extensions-information .message:before{margin-top:0;top:1.8rem}.extensions-information .extensions-container{padding:0 2rem}.extensions-information .list{margin-bottom:1rem}.extensions-information .list select{cursor:pointer}.extensions-information .list select:disabled{background:#ccc;cursor:default}.extensions-information .list .extension-delete{font-size:1.7rem;padding-top:0}.delete-modal-wrap{padding:0 4% 4rem}.delete-modal-wrap h3{font-size:3.4rem;display:inline-block;font-weight:300;margin:0 0 2rem;padding:.9rem 0 0;vertical-align:top}.delete-modal-wrap .actions{padding:3rem 0 0}.page-web-configuration .form-el-insider-wrap{width:auto}.page-web-configuration .form-el-insider{width:15.4rem}.page-web-configuration .form-el-insider-input .form-el-input{width:16.5rem}.customize-your-store .advanced-modules-count,.customize-your-store .advanced-modules-select{padding-left:1.5rem}.customize-your-store .customize-your-store-advanced{min-width:0}.customize-your-store .message-error:before{margin-top:0;top:1.8rem}.customize-your-store .message-error a{color:#333;text-decoration:underline}.customize-your-store .message-error .form-label:before{background:#fff}.customize-your-store .customize-database-clean p{margin-top:2.5rem}.content-install{margin-bottom:2rem}.console{border:1px solid #ccc;font-family:'Courier New',Courier,monospace;font-weight:300;height:20rem;margin:1rem 0 2rem;overflow-y:auto;padding:1.5rem 2rem 2rem;resize:vertical}.console .text-danger{color:#e22626}.console .text-success{color:#090}.console .hidden{display:none}.content-success .btn-prime{margin-top:1.5rem}.jumbo-title{font-size:3.6rem}.jumbo-title .jumbo-icon{font-size:3.8rem;margin-right:.25em;position:relative;top:.15em}.install-database-clean{margin-top:4rem}.install-database-clean .btn{margin-right:1rem}.page-sub-title{margin-bottom:2.1rem;margin-top:3rem}.multiselect-custom{max-width:71.1rem}.content-install{margin-top:3.7rem}.home-page-inner-wrap{margin:0 auto;max-width:91rem}.setup-home-title{margin-bottom:3.9rem;padding-top:1.8rem;text-align:center}.setup-home-item{background-color:#fafafa;border:1px solid #ccc;color:#333;display:block;margin-bottom:2rem;margin-left:1.3rem;margin-right:1.3rem;min-height:30rem;padding:2rem;text-align:center}.setup-home-item:hover{border-color:#8c8c8c;color:#333;text-decoration:none;transition:border-color .1s linear}.setup-home-item:active{-ms-transform:scale(0.99);transform:scale(0.99)}.setup-home-item:before{display:block;font-size:7rem;margin-bottom:3.3rem;margin-top:4rem}.setup-home-item-component:before,.setup-home-item-extension:before{content:'\e612'}.setup-home-item-module:before{content:'\e647'}.setup-home-item-upgrade:before{content:'\e614'}.setup-home-item-configuration:before{content:'\e610'}.setup-home-item-title{display:block;font-size:1.8rem;letter-spacing:.025em;margin-bottom:1rem}.setup-home-item-description{display:block}.extension-manager-wrap{border:1px solid #bbb;margin:0 0 4rem}.extension-manager-account{font-size:2.1rem;display:inline-block;font-weight:400}.extension-manager-title{font-size:3.2rem;background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;color:#41362f;font-weight:600;line-height:1.2;padding:2rem}.extension-manager-content{padding:2.5rem 2rem 2rem}.extension-manager-items{list-style:none;margin:0;text-align:center}.extension-manager-items .btn{border:1px solid #adadad;display:block;margin:1rem auto 0}.extension-manager-items .item-title{font-size:2.1rem;display:inline-block;text-align:left}.extension-manager-items .item-number{font-size:4.1rem;display:inline-block;line-height:.8;margin:0 5px 1.5rem 0;vertical-align:top}.extension-manager-items .item-date{font-size:2.6rem;margin-top:1px}.extension-manager-items .item-date-title{font-size:1.5rem}.extension-manager-items .item-install{margin:0 0 2rem}.sync-login-wrap{padding:0 10% 4rem}.sync-login-wrap .legend{font-size:2.6rem;color:#eb5202;float:left;font-weight:300;line-height:1.2;margin:-1rem 0 2.5rem;position:static;width:100%}.sync-login-wrap .legend._hidden{display:none}.sync-login-wrap .login-header{font-size:3.4rem;font-weight:300;margin:0 0 2rem}.sync-login-wrap .login-header span{display:inline-block;padding:.9rem 0 0;vertical-align:top}.sync-login-wrap h4{font-size:1.4rem;margin:0 0 2rem}.sync-login-wrap .sync-login-steps{margin:0 0 2rem 1.5rem}.sync-login-wrap .sync-login-steps li{padding:0 0 0 1rem}.sync-login-wrap .form-row .form-label{display:inline-block}.sync-login-wrap .form-row .form-label.required{padding-left:1.5rem}.sync-login-wrap .form-row .form-label.required:after{left:0;position:absolute;right:auto}.sync-login-wrap .form-row{max-width:28rem}.sync-login-wrap .form-actions{display:table;margin-top:-1.3rem}.sync-login-wrap .form-actions .links{display:table-header-group}.sync-login-wrap .form-actions .actions{padding:3rem 0 0}@media all and (max-width:1047px){.admin__menu .submenu li{min-width:19.8rem}.nav{padding-bottom:5.38rem;padding-left:1.5rem;text-align:center}.nav-bar{display:inline-block;float:none;margin-right:0;vertical-align:top}.nav .btn-group,.nav-bar-outer-actions{display:inline-block;float:none;margin-top:-8.48rem;text-align:center;vertical-align:top;width:100%}.nav-bar-outer-actions{padding-right:0}.nav-bar-outer-actions .outer-actions-inner-wrap{display:inline-block}.app-updater .nav{padding-bottom:1.7rem}.app-updater .nav-bar-outer-actions{margin-top:2rem}}@media all and (min-width:768px){.page-layout-admin-2columns-left .page-columns{margin-left:-30px}.page-layout-admin-2columns-left .page-columns:after{clear:both;content:'';display:table}.page-layout-admin-2columns-left .page-columns .main-col{width:calc((100%) * .75 - 30px);float:right}.page-layout-admin-2columns-left .page-columns .side-col{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9{float:left}.col-m-12{width:100%}.col-m-11{width:91.66666667%}.col-m-10{width:83.33333333%}.col-m-9{width:75%}.col-m-8{width:66.66666667%}.col-m-7{width:58.33333333%}.col-m-6{width:50%}.col-m-5{width:41.66666667%}.col-m-4{width:33.33333333%}.col-m-3{width:25%}.col-m-2{width:16.66666667%}.col-m-1{width:8.33333333%}.col-m-pull-12{right:100%}.col-m-pull-11{right:91.66666667%}.col-m-pull-10{right:83.33333333%}.col-m-pull-9{right:75%}.col-m-pull-8{right:66.66666667%}.col-m-pull-7{right:58.33333333%}.col-m-pull-6{right:50%}.col-m-pull-5{right:41.66666667%}.col-m-pull-4{right:33.33333333%}.col-m-pull-3{right:25%}.col-m-pull-2{right:16.66666667%}.col-m-pull-1{right:8.33333333%}.col-m-pull-0{right:auto}.col-m-push-12{left:100%}.col-m-push-11{left:91.66666667%}.col-m-push-10{left:83.33333333%}.col-m-push-9{left:75%}.col-m-push-8{left:66.66666667%}.col-m-push-7{left:58.33333333%}.col-m-push-6{left:50%}.col-m-push-5{left:41.66666667%}.col-m-push-4{left:33.33333333%}.col-m-push-3{left:25%}.col-m-push-2{left:16.66666667%}.col-m-push-1{left:8.33333333%}.col-m-push-0{left:auto}.col-m-offset-12{margin-left:100%}.col-m-offset-11{margin-left:91.66666667%}.col-m-offset-10{margin-left:83.33333333%}.col-m-offset-9{margin-left:75%}.col-m-offset-8{margin-left:66.66666667%}.col-m-offset-7{margin-left:58.33333333%}.col-m-offset-6{margin-left:50%}.col-m-offset-5{margin-left:41.66666667%}.col-m-offset-4{margin-left:33.33333333%}.col-m-offset-3{margin-left:25%}.col-m-offset-2{margin-left:16.66666667%}.col-m-offset-1{margin-left:8.33333333%}.col-m-offset-0{margin-left:0}.page-columns{margin-left:-30px}.page-columns:after{clear:both;content:'';display:table}.page-columns .page-inner-content{width:calc((100%) * .75 - 30px);float:right}.page-columns .page-inner-sidebar{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}}@media all and (min-width:1048px){.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9{float:left}.col-l-12{width:100%}.col-l-11{width:91.66666667%}.col-l-10{width:83.33333333%}.col-l-9{width:75%}.col-l-8{width:66.66666667%}.col-l-7{width:58.33333333%}.col-l-6{width:50%}.col-l-5{width:41.66666667%}.col-l-4{width:33.33333333%}.col-l-3{width:25%}.col-l-2{width:16.66666667%}.col-l-1{width:8.33333333%}.col-l-pull-12{right:100%}.col-l-pull-11{right:91.66666667%}.col-l-pull-10{right:83.33333333%}.col-l-pull-9{right:75%}.col-l-pull-8{right:66.66666667%}.col-l-pull-7{right:58.33333333%}.col-l-pull-6{right:50%}.col-l-pull-5{right:41.66666667%}.col-l-pull-4{right:33.33333333%}.col-l-pull-3{right:25%}.col-l-pull-2{right:16.66666667%}.col-l-pull-1{right:8.33333333%}.col-l-pull-0{right:auto}.col-l-push-12{left:100%}.col-l-push-11{left:91.66666667%}.col-l-push-10{left:83.33333333%}.col-l-push-9{left:75%}.col-l-push-8{left:66.66666667%}.col-l-push-7{left:58.33333333%}.col-l-push-6{left:50%}.col-l-push-5{left:41.66666667%}.col-l-push-4{left:33.33333333%}.col-l-push-3{left:25%}.col-l-push-2{left:16.66666667%}.col-l-push-1{left:8.33333333%}.col-l-push-0{left:auto}.col-l-offset-12{margin-left:100%}.col-l-offset-11{margin-left:91.66666667%}.col-l-offset-10{margin-left:83.33333333%}.col-l-offset-9{margin-left:75%}.col-l-offset-8{margin-left:66.66666667%}.col-l-offset-7{margin-left:58.33333333%}.col-l-offset-6{margin-left:50%}.col-l-offset-5{margin-left:41.66666667%}.col-l-offset-4{margin-left:33.33333333%}.col-l-offset-3{margin-left:25%}.col-l-offset-2{margin-left:16.66666667%}.col-l-offset-1{margin-left:8.33333333%}.col-l-offset-0{margin-left:0}}@media all and (min-width:1440px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-pull-12{right:100%}.col-xl-pull-11{right:91.66666667%}.col-xl-pull-10{right:83.33333333%}.col-xl-pull-9{right:75%}.col-xl-pull-8{right:66.66666667%}.col-xl-pull-7{right:58.33333333%}.col-xl-pull-6{right:50%}.col-xl-pull-5{right:41.66666667%}.col-xl-pull-4{right:33.33333333%}.col-xl-pull-3{right:25%}.col-xl-pull-2{right:16.66666667%}.col-xl-pull-1{right:8.33333333%}.col-xl-pull-0{right:auto}.col-xl-push-12{left:100%}.col-xl-push-11{left:91.66666667%}.col-xl-push-10{left:83.33333333%}.col-xl-push-9{left:75%}.col-xl-push-8{left:66.66666667%}.col-xl-push-7{left:58.33333333%}.col-xl-push-6{left:50%}.col-xl-push-5{left:41.66666667%}.col-xl-push-4{left:33.33333333%}.col-xl-push-3{left:25%}.col-xl-push-2{left:16.66666667%}.col-xl-push-1{left:8.33333333%}.col-xl-push-0{left:auto}.col-xl-offset-12{margin-left:100%}.col-xl-offset-11{margin-left:91.66666667%}.col-xl-offset-10{margin-left:83.33333333%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-8{margin-left:66.66666667%}.col-xl-offset-7{margin-left:58.33333333%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-5{margin-left:41.66666667%}.col-xl-offset-4{margin-left:33.33333333%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-2{margin-left:16.66666667%}.col-xl-offset-1{margin-left:8.33333333%}.col-xl-offset-0{margin-left:0}}@media all and (max-width:767px){.abs-clearer-mobile:after,.nav-bar:after{clear:both;content:'';display:table}.list-definition>dt{float:none}.list-definition>dd{margin-left:0}.form-row .form-label{text-align:left}.form-row .form-label.required:after{position:static}.nav{padding-bottom:0;padding-left:0;padding-right:0}.nav-bar-outer-actions{margin-top:0}.nav-bar{display:block;margin-bottom:0;margin-left:auto;margin-right:auto;width:30.9rem}.nav-bar:before{display:none}.nav-bar>li{float:left;min-height:9rem}.nav-bar>li:after{display:none}.nav-bar>li:nth-child(4n){clear:both}.nav-bar a{line-height:1.4}.tooltip{display:none!important}.readiness-check-content{margin-right:2rem}.readiness-check-side{padding:2rem 0;position:static}.form-el-insider,.form-el-insider-wrap,.page-web-configuration .form-el-insider-input,.page-web-configuration .form-el-insider-input .form-el-input{display:block;width:100%}}@media all and (max-width:479px){.nav-bar{width:23.175rem}.nav-bar>li{width:7.725rem}.nav .btn-group .btn-wrap-try-again,.nav-bar-outer-actions .btn-wrap-try-again{clear:both;display:block;float:none;margin-left:auto;margin-right:auto;margin-top:1rem;padding-top:1rem}} diff --git a/setup/src/Magento/Setup/Console/Command/AbstractDependenciesCommand.php b/setup/src/Magento/Setup/Console/Command/AbstractDependenciesCommand.php index 0148152b0eaa6..fa4c9c897e7b1 100644 --- a/setup/src/Magento/Setup/Console/Command/AbstractDependenciesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/AbstractDependenciesCommand.php @@ -1,6 +1,6 @@ objectManagerFactory = $objectManagerFactory; $this->validator = $validator; - $this->objectManager = $objectManager; + $this->objectManager = $objectManagerProvider->get(); + parent::__construct(); } @@ -373,6 +366,7 @@ private function getDeployableEntities($entities, $includedEntities, $excludedEn /** * {@inheritdoc} * @throws \InvalidArgumentException + * @throws LocalizedException */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -394,9 +388,9 @@ protected function execute(InputInterface $input, OutputInterface $output) list ($deployableLanguages, $deployableAreaThemeMap, $requestedThemes) = $this->prepareDeployableEntities($filesUtil); - $output->writeln("Requested languages: " . implode(', ', $deployableLanguages)); - $output->writeln("Requested areas: " . implode(', ', array_keys($deployableAreaThemeMap))); - $output->writeln("Requested themes: " . implode(', ', $requestedThemes)); + $output->writeln('Requested languages: ' . implode(', ', $deployableLanguages)); + $output->writeln('Requested areas: ' . implode(', ', array_keys($deployableAreaThemeMap))); + $output->writeln('Requested themes: ' . implode(', ', $requestedThemes)); /** @var $deployManager DeployManager */ $deployManager = $this->objectManager->create( @@ -415,11 +409,13 @@ protected function execute(InputInterface $input, OutputInterface $output) } } + $this->mockCache(); return $deployManager->deploy(); } /** * @param Files $filesUtil + * @throws \InvalidArgumentException * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -476,4 +472,18 @@ private function prepareDeployableEntities($filesUtil) return [$deployableLanguages, $deployableAreaThemeMap, $requestedThemes]; } + + /** + * Mock Cache class with dummy implementation + * + * @return void + */ + private function mockCache() + { + $this->objectManager->configure([ + 'preferences' => [ + Cache::class => DummyCache::class + ] + ]); + } } diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php index 3f577d9b31187..bc443379a6bd4 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -1,6 +1,6 @@ componentRegistrar->getPaths(ComponentRegistrar::MODULE); $libraryPaths = $this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY); - $generationPath = $this->directoryList->getPath(DirectoryList::GENERATION); + $generationPath = $this->directoryList->getPath(DirectoryList::GENERATED_CODE); $this->objectManager->get(\Magento\Framework\App\Cache::class)->clean(); $compiledPathsList = [ @@ -152,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->cleanupFilesystem( [ DirectoryList::CACHE, - DirectoryList::DI, + DirectoryList::GENERATED_METADATA, ] ); foreach ($operations as $operationCode => $arguments) { diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php deleted file mode 100644 index ac6a43189d7ab..0000000000000 --- a/setup/src/Magento/Setup/Console/Command/DiCompileMultiTenantCommand.php +++ /dev/null @@ -1,493 +0,0 @@ -objectManager = $objectManagerProvider->get(); - $this->directoryList = $directoryList; - $this->componentRegistrar = $componentRegistrar; - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $options = [ - new InputOption( - self::INPUT_KEY_SERIALIZER, - null, - InputOption::VALUE_REQUIRED, - 'Serializer function that should be used (' . self::SERIALIZER_VALUE_SERIALIZE . '|' - . self::SERIALIZER_VALUE_IGBINARY . ') default: ' . self::SERIALIZER_VALUE_SERIALIZE - ), - new InputOption( - self::INPUT_KEY_EXTRA_CLASSES_FILE, - null, - InputOption::VALUE_REQUIRED, - 'Path to file with extra proxies and factories to generate' - ), - new InputOption( - self::INPUT_KEY_GENERATION, - null, - InputOption::VALUE_REQUIRED, - 'Absolute path to generated classes, /var/generation by default' - ), - new InputOption( - self::INPUT_KEY_DI, - null, - InputOption::VALUE_REQUIRED, - 'Absolute path to DI definitions directory, /var/di by default' - ), - new InputOption( - self::INPUT_KEY_EXCLUDE_PATTERN, - null, - InputOption::VALUE_REQUIRED, - 'Allows to exclude Paths from compilation (default is #[\\\\/]m1[\\\\/]#i)' - ), - ]; - $this->setName(self::NAME) - ->setDescription( - 'Generates all non-existing proxies and factories, and pre-compile class definitions, ' - . 'inheritance information and plugin definitions' - ) - ->setDefinition($options); - parent::configure(); - } - - /** - * Get module directories exclude patterns - * - * @return array - */ - private function getModuleExcludePatterns() - { - $modulesExcludePatterns = []; - foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $modulePath) { - $modulesExcludePatterns[] = "#^" . $modulePath . "/Test#"; - } - return $modulesExcludePatterns; - } - - /** - * Get library directories exclude patterns - * - * @return array - */ - private function getLibraryExcludePatterns() - { - $libraryExcludePatterns = []; - foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libraryPath) { - $libraryExcludePatterns[] = "#^" . $libraryPath . "/([\\w]+/)?Test#"; - } - return $libraryExcludePatterns; - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $errors = $this->validate($input); - if ($errors) { - $output->writeln($errors); - return; - } - - $generationDir = $input->getOption(self::INPUT_KEY_GENERATION) ? $input->getOption(self::INPUT_KEY_GENERATION) - : $this->directoryList->getPath(DirectoryList::GENERATION); - $modulesExcludePatterns = $this->getModuleExcludePatterns(); - $testExcludePatterns = [ - "#^" . $this->directoryList->getPath(DirectoryList::SETUP) . "/[\\w]+/[\\w]+/Test#", - "#^" . $this->directoryList->getRoot() . "/dev/tools/Magento/Tools/[\\w]+/Test#" - ]; - $librariesExcludePatterns = $this->getLibraryExcludePatterns(); - $testExcludePatterns = array_merge($testExcludePatterns, $modulesExcludePatterns, $librariesExcludePatterns); - $fileExcludePatterns = $input->getOption('exclude-pattern') ? - [$input->getOption(self::INPUT_KEY_EXCLUDE_PATTERN)] : ['#[\\\\/]M1[\\\\/]#i']; - $fileExcludePatterns = array_merge($fileExcludePatterns, $testExcludePatterns); - /** @var Writer\Console logWriter Writer model for success messages */ - $logWriter = new Writer\Console($output); - $this->log = new Log($logWriter, $logWriter); - AutoloaderRegistry::getAutoloader()->addPsr4('Magento\\', $generationDir . '/Magento/'); - // 1 Code generation - $this->generateCode($generationDir, $fileExcludePatterns, $input); - // 2. Compilation - $this->compileCode($generationDir, $fileExcludePatterns, $input); - //Reporter - $this->log->report(); - if (!$this->log->hasError()) { - $output->writeln( - 'On *nix systems, verify the Magento application has permissions to modify files ' - . 'created by the compiler in the "var" directory. For instance, if you run the Magento application ' - . 'using Apache, the owner of the files in the "var" directory should be the Apache user (example ' - . 'command: "chown -R www-data:www-data /var" where MAGENTO_ROOT is the Magento ' - . 'root directory).' - ); - } - } - - /** - * Generate Code - * - * @param string $generationDir - * @param array $fileExcludePatterns - * @param InputInterface $input - * @return void - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function generateCode($generationDir, $fileExcludePatterns, $input) - { - // 1.1 Code scan - $filePatterns = ['php' => '/.*\.php$/', 'di' => '/\/etc\/([a-zA-Z_]*\/di|di)\.xml$/']; - $directoryScanner = new Scanner\DirectoryScanner(); - foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $codeScanDir) { - $this->files = array_merge_recursive( - $this->files, - $directoryScanner->scan($codeScanDir, $filePatterns, $fileExcludePatterns) - ); - } - $this->files['di'][] = $this->directoryList->getPath( - \Magento\Framework\App\Filesystem\DirectoryList::CONFIG - ) . '/di.xml'; - $this->files['additional'] = [$input->getOption(self::INPUT_KEY_EXTRA_CLASSES_FILE)]; - $repositoryScanner = new Scanner\RepositoryScanner(); - $repositories = $repositoryScanner->collectEntities($this->files['di']); - $scanner = new Scanner\CompositeScanner(); - $scanner->addChild(new Scanner\PhpScanner($this->log), 'php'); - $scanner->addChild(new Scanner\XmlScanner($this->log), 'di'); - $scanner->addChild(new Scanner\ArrayScanner(), 'additional'); - $this->entities = $scanner->collectEntities($this->files); - $interceptorScanner = new Scanner\XmlInterceptorScanner(); - $this->entities['interceptors'] = $interceptorScanner->collectEntities($this->files['di']); - // 1.2 Generation of Factory and Additional Classes - $generatorIo = $this->objectManager->create( - \Magento\Framework\Code\Generator\Io::class, - ['generationDirectory' => $generationDir] - ); - $this->generator = $this->objectManager->create( - \Magento\Framework\Code\Generator::class, - ['ioObject' => $generatorIo] - ); - /** Initialize object manager for code generation based on configs */ - $this->generator->setObjectManager($this->objectManager); - $generatorAutoloader = new \Magento\Framework\Code\Generator\Autoloader($this->generator); - spl_autoload_register([$generatorAutoloader, 'load']); - - foreach ($repositories as $entityName) { - switch ($this->generator->generateClass($entityName)) { - case CodeGenerator::GENERATION_SUCCESS: - $this->log->add(Log::GENERATION_SUCCESS, $entityName); - break; - case CodeGenerator::GENERATION_ERROR: - $this->log->add(Log::GENERATION_ERROR, $entityName); - break; - case CodeGenerator::GENERATION_SKIP: - default: - //no log - break; - } - } - foreach (['php', 'additional'] as $type) { - sort($this->entities[$type]); - foreach ($this->entities[$type] as $entityName) { - switch ($this->generator->generateClass($entityName)) { - case CodeGenerator::GENERATION_SUCCESS: - $this->log->add(Log::GENERATION_SUCCESS, $entityName); - break; - case CodeGenerator::GENERATION_ERROR: - $this->log->add(Log::GENERATION_ERROR, $entityName); - break; - case CodeGenerator::GENERATION_SKIP: - default: - //no log - break; - } - } - } - } - - /** - * Compile Code - * - * @param string $generationDir - * @param array $fileExcludePatterns - * @param InputInterface $input - * @return void - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function compileCode($generationDir, $fileExcludePatterns, $input) - { - $diDir = $input->getOption(self::INPUT_KEY_DI) ? $input->getOption(self::INPUT_KEY_DI) : - $this->directoryList->getPath(DirectoryList::DI); - $relationsFile = $diDir . '/relations.ser'; - $pluginDefFile = $diDir . '/plugins.ser'; - $compilationDirs = [ - $this->directoryList->getPath(DirectoryList::SETUP) . '/Magento/Setup/Module', - $this->directoryList->getRoot() . '/dev/tools/Magento/Tools', - ]; - $compilationDirs = array_merge( - $compilationDirs, - $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE), - $this->componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) - ); - $serializer = $input->getOption(self::INPUT_KEY_SERIALIZER) == Igbinary::NAME ? new Igbinary() : new Standard(); - // 2.1 Code scan - $validator = new \Magento\Framework\Code\Validator(); - $validator->add(new \Magento\Framework\Code\Validator\ConstructorIntegrity()); - $validator->add(new \Magento\Framework\Code\Validator\ContextAggregation()); - $classesScanner = new \Magento\Setup\Module\Di\Code\Reader\ClassesScanner(); - $classesScanner->addExcludePatterns($fileExcludePatterns); - $directoryInstancesNamesList = new \Magento\Setup\Module\Di\Code\Reader\Decorator\Directory( - $this->log, - new \Magento\Framework\Code\Reader\ClassReader(), - $classesScanner, - $validator, - $generationDir - ); - foreach ($compilationDirs as $path) { - if (is_readable($path)) { - $directoryInstancesNamesList->getList($path); - } - } - $inheritanceScanner = new Scanner\InheritanceInterceptorScanner( - new \Magento\Framework\ObjectManager\InterceptableValidator() - ); - $this->entities['interceptors'] = $inheritanceScanner->collectEntities( - get_declared_classes(), - $this->entities['interceptors'] - ); - // 2.1.1 Generation of Proxy and Interceptor Classes - foreach (['interceptors', 'di'] as $type) { - foreach ($this->entities[$type] as $entityName) { - switch ($this->generator->generateClass($entityName)) { - case CodeGenerator::GENERATION_SUCCESS: - $this->log->add(Log::GENERATION_SUCCESS, $entityName); - break; - case CodeGenerator::GENERATION_ERROR: - $this->log->add(Log::GENERATION_ERROR, $entityName); - break; - case CodeGenerator::GENERATION_SKIP: - default: - //no log - break; - } - } - } - //2.1.2 Compile relations for Proxy/Interceptor classes - $directoryInstancesNamesList->getList($generationDir); - $relations = $directoryInstancesNamesList->getRelations(); - // 2.2 Compression - $relationsFileDir = dirname($relationsFile); - if (!file_exists($relationsFileDir)) { - mkdir($relationsFileDir, 0777, true); - } - $relations = array_filter($relations); - file_put_contents($relationsFile, $serializer->serialize($relations)); - // 3. Plugin Definition Compilation - $pluginScanner = new Scanner\CompositeScanner(); - $pluginScanner->addChild(new Scanner\PluginScanner(), 'di'); - $pluginDefinitions = []; - $pluginList = $pluginScanner->collectEntities($this->files); - $pluginDefinitionList = new \Magento\Framework\Interception\Definition\Runtime(); - foreach ($pluginList as $type => $entityList) { - foreach ($entityList as $entity) { - $pluginDefinitions[ltrim($entity, '\\')] = $pluginDefinitionList->getMethodList($entity); - } - } - $outputContent = $serializer->serialize($pluginDefinitions); - $pluginDefFileDir = dirname($pluginDefFile); - if (!file_exists($pluginDefFileDir)) { - mkdir($pluginDefFileDir, 0777, true); - } - file_put_contents($pluginDefFile, $outputContent); - } - - /** - * Check if all option values provided by the user are valid - * - * @param InputInterface $input - * @return string[] - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function validate(InputInterface $input) - { - $errors = []; - $options = $input->getOptions(); - foreach ($options as $key => $value) { - if (!$value) { - continue; - } - switch ($key) { - case self::INPUT_KEY_SERIALIZER: - if (($value !== self::SERIALIZER_VALUE_SERIALIZE) && ($value !== self::SERIALIZER_VALUE_IGBINARY)) { - $errors[] = 'Invalid value for command option \'' . self::INPUT_KEY_SERIALIZER - . '\'. Possible values (' . self::SERIALIZER_VALUE_SERIALIZE . '|' - . self::SERIALIZER_VALUE_IGBINARY . ').'; - } - break; - case self::INPUT_KEY_EXTRA_CLASSES_FILE: - if (!file_exists($value)) { - $errors[] = 'Path does not exist for the value of command option \'' - . self::INPUT_KEY_EXTRA_CLASSES_FILE . '\'.'; - } - break; - case self::INPUT_KEY_GENERATION: - $errorMsg = $this->validateOutputPath($value, self::INPUT_KEY_GENERATION); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } - break; - case self::INPUT_KEY_DI: - $errorMsg = $this->validateOutputPath($value, self::INPUT_KEY_DI); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } - break; - case self::INPUT_KEY_EXCLUDE_PATTERN: - if (@preg_match($value, null) === false) { - $errors[] = 'Invalid pattern for command option \'' . self::INPUT_KEY_EXCLUDE_PATTERN - . '\'.'; - } - break; - } - } - return $errors; - } - - /** - * Validate output path based on type - * - * @param string $value - * @param string $type - * @return string - */ - private function validateOutputPath($value, $type) - { - $errorMsg = ''; - if (!file_exists($value)) { - $errorMsg = 'Path does not exist for the value of command option \'' . $type . '\'.'; - } - if (file_exists($value) && !is_writeable($value)) { - $errorMsg .= 'Non-writable directory is provided by the value of command option \'' - . $type . '\'.'; - - } - return $errorMsg; - } -} diff --git a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php index d91a1633ef038..9bc28bb97a905 100644 --- a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php @@ -1,11 +1,13 @@ fixtureModel; + $fixtureModel->loadConfig($input->getArgument(self::PROFILE_ARGUMENT)); $fixtureModel->initObjectManager(); $fixtureModel->loadFixtures(); - $fixtureModel->loadConfig($input->getArgument(self::PROFILE_ARGUMENT)); $output->writeln('Generating profile with following params:'); - foreach ($fixtureModel->getParamLabels() as $configKey => $label) { - $output->writeln(' |- ' . $label . ': ' . $fixtureModel->getValue($configKey) . ''); + foreach ($fixtureModel->getFixtures() as $fixture) { + $fixture->printInfo($output); } /** @var $config \Magento\Indexer\Model\Config */ @@ -95,14 +97,16 @@ protected function execute(InputInterface $input, OutputInterface $output) } foreach ($fixtureModel->getFixtures() as $fixture) { - $output->write($fixture->getActionTitle() . '... '); + $output->write('' . $fixture->getActionTitle() . '... '); $startTime = microtime(true); - $fixture->execute(); + $fixture->execute($output); $endTime = microtime(true); $resultTime = $endTime - $startTime; - $output->writeln(' done in ' . gmdate('H:i:s', $resultTime)); + $output->writeln(' done in ' . gmdate('H:i:s', $resultTime) . ''); } + $this->clearChangelog(); + foreach ($indexerListIds as $indexerId) { /** @var $indexer \Magento\Indexer\Model\Indexer */ $indexer = $indexerRegistry->get($indexerId['indexer_id']); @@ -123,4 +127,25 @@ protected function execute(InputInterface $input, OutputInterface $output) return \Magento\Framework\Console\Cli::RETURN_FAILURE; } } + + /** + * Clear changelog after generation + * + * @return void + */ + private function clearChangelog() + { + $viewConfig = $this->fixtureModel->getObjectManager()->create(CollectionInterface::class); + + /* @var ResourceConnection $resource */ + $resource = $this->fixtureModel->getObjectManager()->get(ResourceConnection::class); + + foreach ($viewConfig as $view) { + /* @var \Magento\Framework\Mview\ViewInterface $view */ + $changeLogTableName = $resource->getTableName($view->getChangelog()->getName()); + if ($resource->getConnection()->isTableExists($changeLogTableName)) { + $resource->getConnection()->truncateTable($changeLogTableName); + } + } + } } diff --git a/setup/src/Magento/Setup/Console/Command/I18nCollectPhrasesCommand.php b/setup/src/Magento/Setup/Console/Command/I18nCollectPhrasesCommand.php index 4731f9c4c5de4..c7cd5d6d9b235 100644 --- a/setup/src/Magento/Setup/Console/Command/I18nCollectPhrasesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/I18nCollectPhrasesCommand.php @@ -1,6 +1,6 @@ installerFactory = $installerFactory; $this->deploymentConfig = $deploymentConfig; $this->objectManager = $objectManagerProvider->get(); + $this->localeValidator = $localeValidator; + $this->timezoneValidator = $timezoneValidator; + $this->currencyValidator = $currencyValidator; + $this->urlValidator = $urlValidator; parent::__construct(); } @@ -173,6 +205,7 @@ public function getOptionsList() public function validate(InputInterface $input) { $errors = []; + $errorMsg = ''; $options = $input->getOptions(); foreach ($options as $key => $value) { if (!$value) { @@ -180,99 +213,69 @@ public function validate(InputInterface $input) } switch ($key) { case StoreConfigurationDataMapper::KEY_BASE_URL: - /** @var Validator $url */ if (strcmp($value, '{{base_url}}') == 0) { break; } - $url = $this->objectManager->get(\Magento\Framework\Url\Validator::class); - if (!$url->isValid($value)) { - $errorMsgs = $url->getMessages(); - $errors[] = '' . 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL - . '\': ' . $errorMsgs[Validator::INVALID_URL] .''; - } + $errorMsg = $this->validateUrl( + $value, + StoreConfigurationDataMapper::KEY_BASE_URL, + ['http', 'https'] + ); + break; case StoreConfigurationDataMapper::KEY_LANGUAGE: - /** @var Locale $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Locale::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_LANGUAGE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->localeValidator, + $value, + StoreConfigurationDataMapper::KEY_LANGUAGE + ); break; case StoreConfigurationDataMapper::KEY_TIMEZONE: - /** @var Timezone $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Timezone::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_TIMEZONE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->timezoneValidator, + $value, + StoreConfigurationDataMapper::KEY_TIMEZONE + ); break; case StoreConfigurationDataMapper::KEY_CURRENCY: - /** @var Currency $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Currency::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_CURRENCY); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->currencyValidator, + $value, + StoreConfigurationDataMapper::KEY_CURRENCY + ); break; case StoreConfigurationDataMapper::KEY_USE_SEF_URL: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_USE_SEF_URL); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_IS_SECURE: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_IS_SECURE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_BASE_URL_SECURE: - try { - /** @var Validator $url */ - $url = $this->objectManager->get(\Magento\Framework\Url\Validator::class); - $errorMsgs = ''; - if (!$url->isValid($value)) { - $errorMsgs = $url->getMessages(); - if (!empty($errorMsgs)) { - $errors[] = '' . 'Command option \'' - . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': ' . $errorMsgs[Validator::INVALID_URL] .''; - } - } - if (empty($errorMsgs) && strpos($value, 'https:') === false) { - throw new LocalizedException(new \Magento\Framework\Phrase("Invalid secure URL.")); - } - } catch (LocalizedException $e) { - $errors[] = '' . 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': ' . $e->getLogMessage() .''; - } + $errorMsg = $this->validateUrl( + $value, + StoreConfigurationDataMapper::KEY_BASE_URL_SECURE, + ['https'] + ); break; case StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY: $errorMsg = $this->validateBinaryValue( $value, StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY ); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_JS_LOGGING: $errorMsg = $this->validateBinaryValue( $value, StoreConfigurationDataMapper::KEY_JS_LOGGING ); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; } + if ($errorMsg !== '') { + $errors[] = $errorMsg; + } } return $errors; } @@ -296,7 +299,7 @@ private function validateBinaryValue($value, $key) /** * Validate codes for languages, currencies or timezones * - * @param Locale|Timezone|Currency $lists + * @param LocaleValidator|TimezoneValidator|CurrencyValidator $lists * @param string $code * @param string $type * @return string @@ -310,4 +313,31 @@ private function validateCodes($lists, $code, $type) } return $errorMsg; } + + /** + * Validate URL + * + * @param string $url + * @param string $option + * @param array $allowedSchemes + * @return string + */ + private function validateUrl($url, $option, array $allowedSchemes) + { + $errorMsg = ''; + + if (!$this->urlValidator->isValid($url, $allowedSchemes)) { + $errorTemplate = 'Command option \'%s\': Invalid URL \'%s\'.' + . ' Domain Name should contain only letters, digits and hyphen.' + . ' And you should use only following schemes: \'%s\'.'; + $errorMsg = sprintf( + $errorTemplate, + $option, + $url, + implode(', ', $allowedSchemes) + ); + } + + return $errorMsg; + } } diff --git a/setup/src/Magento/Setup/Console/Command/MaintenanceAllowIpsCommand.php b/setup/src/Magento/Setup/Console/Command/MaintenanceAllowIpsCommand.php index 40823d5a2b162..d1aec5fa8eac2 100644 --- a/setup/src/Magento/Setup/Console/Command/MaintenanceAllowIpsCommand.php +++ b/setup/src/Magento/Setup/Console/Command/MaintenanceAllowIpsCommand.php @@ -1,6 +1,6 @@ installSchema(); $installer->installDataFixtures(); if (!$keepGenerated) { - $output->writeln('Please re-run Magento compile command'); + $output->writeln('Please re-run Magento compile command. Use the command "setup:di:compile"'); } return \Magento\Framework\Console\Cli::RETURN_SUCCESS; diff --git a/setup/src/Magento/Setup/Console/CommandList.php b/setup/src/Magento/Setup/Console/CommandList.php index 4bb5ac0181a79..d986160fa4f13 100644 --- a/setup/src/Magento/Setup/Console/CommandList.php +++ b/setup/src/Magento/Setup/Console/CommandList.php @@ -1,6 +1,6 @@ serviceManager = $serviceManager; - $this->input = $input; + $this->serviceManager = $serviceManager; + $this->input = $input; $this->filesystemDriver = $filesystemDriver; } /** - * Determine whether a CLI command is for compilation, and if so, clear the directory + * Determine whether a CLI command is for compilation, and if so, clear the directory. * - * @throws \Magento\Framework\Exception\FileSystemException + * @throws FileSystemException if generation directory is read-only * @return void */ public function handleCompilerEnvironment() @@ -60,8 +76,14 @@ public function handleCompilerEnvironment() ? $mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $mageDirs); - $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATION); - $compileDirList[] = $directoryList->getPath(DirectoryList::DI); + $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_CODE); + $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_METADATA); + + if (!$this->getGenerationDirectoryAccess()->check()) { + throw new FileSystemException( + new Phrase('Generation directory can not be written.') + ); + } foreach ($compileDirList as $compileDir) { if ($this->filesystemDriver->isExists($compileDir)) { @@ -69,4 +91,18 @@ public function handleCompilerEnvironment() } } } + + /** + * Retrieves generation directory access checker. + * + * @return GenerationDirectoryAccess the generation directory access checker + */ + private function getGenerationDirectoryAccess() + { + if (null === $this->generationDirectoryAccess) { + $this->generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager); + } + + return $this->generationDirectoryAccess; + } } diff --git a/setup/src/Magento/Setup/Controller/AddDatabase.php b/setup/src/Magento/Setup/Controller/AddDatabase.php index 65fb1b684f35b..c3cab8a47b2c8 100644 --- a/setup/src/Magento/Setup/Controller/AddDatabase.php +++ b/setup/src/Magento/Setup/Controller/AddDatabase.php @@ -1,6 +1,6 @@ urlValidator = $urlValidator; + } + + /** + * Validate URL + * + * @return JsonModel + */ + public function indexAction() + { + $params = Json::decode($this->getRequest()->getContent(), Json::TYPE_ARRAY); + $result = ['successUrl' => false, 'successSecureUrl' => true]; + + $hasBaseUrl = isset($params['address']['actual_base_url']); + $hasSecureBaseUrl = isset($params['https']['text']); + $hasSecureAdminUrl = !empty($params['https']['admin']); + $hasSecureFrontUrl = !empty($params['https']['front']); + $schemes = ['http', 'https']; + + // Validating of Base URL + if ($hasBaseUrl && $this->urlValidator->isValid($params['address']['actual_base_url'], $schemes)) { + $result['successUrl'] = true; + } + + // Validating of Secure Base URL + if ($hasSecureAdminUrl || $hasSecureFrontUrl) { + if (!($hasSecureBaseUrl && $this->urlValidator->isValid($params['https']['text'], $schemes))) { + $result['successSecureUrl'] = false; + } + } + + return new JsonModel($result); + } +} diff --git a/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php index 6833c1fba0c11..01461dd499586 100644 --- a/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php +++ b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php @@ -1,6 +1,6 @@ userFactory = $userFactory; + $this->roleFactory = $roleFactory; + $this->userCollectionFactory = $userCollectionFactory; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $adminUsersNumber = $this->fixtureModel->getValue('admin_users', 0); + $adminUsersStartIndex = $this->userCollectionFactory->create()->getSize(); + + if ($adminUsersStartIndex >= $adminUsersNumber) { + return; + } + + $defaultAdminUser = $this->userFactory->create()->loadByUsername('admin'); + $defaultAdminRole = $this->roleFactory->create()->load($defaultAdminUser->getAclRole()); + + for ($i = $adminUsersStartIndex; $i <= $adminUsersNumber; $i++) { + $adminUser = $this->userFactory->create(); + $adminUser + ->setEmail('admin' . $i . '@example.com') + ->setFirstName('Firstname') + ->setLastName('Lastname') + ->setUserName('admin' . $i) + ->setPassword('123123q') + ->setIsActive(1); + $adminUser->save(); + + $role = $this->roleFactory->create(); + $role + ->setUserId($adminUser->getId()) + ->setRoleName('admin') + ->setRoleType($defaultAdminRole->getRoleType()) + ->setUserType($defaultAdminRole->getUserType()) + ->setTreeLevel($defaultAdminRole->getTreeLevel()) + ->setSortOrder($defaultAdminRole->getSortOrder()) + ->setParentId(1); + $role->save(); + } + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating admin users'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'admin_users' => 'Admin Users' + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSet/AttributeSetFixture.php b/setup/src/Magento/Setup/Fixtures/AttributeSet/AttributeSetFixture.php new file mode 100644 index 0000000000000..623a1844dd5f8 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/AttributeSet/AttributeSetFixture.php @@ -0,0 +1,171 @@ +attributeRepository = $attributeRepository; + $this->attributeManagement = $attributeManagement; + $this->attributeFactory = $attributeFactory; + $this->optionFactory = $optionFactory; + $this->attributeSetFactory = $attributeSetFactory; + $this->attributeGroupFactory = $attributeGroupFactory; + $this->attributeSetManagement = $attributeSetManagement; + $this->attributeGroupRepository = $attributeGroupRepository; + } + + /** + * Create Attribute Set based on raw data. + * + * @param array $attributeSetData + * @param int $sortOrder + * @return array + */ + public function createAttributeSet(array $attributeSetData, $sortOrder = 3) + { + /** @var \Magento\Eav\Api\Data\AttributeSetInterface $attributeSet */ + $attributeSet = $this->attributeSetFactory->create(); + $attributeSet->setAttributeSetName($attributeSetData['name']); + $attributeSet->setEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE); + + try { + $attributeSet = $this->attributeSetManagement->create($attributeSet, 4); + } catch (\Exception $e) { + return $this->getFormattedAttributeSetData($attributeSetData); + } + $attributeSetId = $attributeSet->getAttributeSetId(); + + /** @var \Magento\Eav\Api\Data\AttributeGroupInterface $attributeGroup */ + $attributeGroup = $this->attributeGroupFactory->create(); + $attributeGroup->setAttributeGroupName($attributeSet->getAttributeSetName() . ' - Group'); + $attributeGroup->setAttributeSetId($attributeSetId); + $this->attributeGroupRepository->save($attributeGroup); + $attributeGroupId = $attributeGroup->getAttributeGroupId(); + + $attributesData = array_key_exists(0, $attributeSetData['attributes']['attribute']) + ? $attributeSetData['attributes']['attribute'] : [$attributeSetData['attributes']['attribute']]; + foreach ($attributesData as $attributeData) { + //Create Attribute + $optionsData = array_key_exists(0, $attributeData['options']['option']) + ? $attributeData['options']['option'] : [$attributeData['options']['option']]; + $options = []; + foreach ($optionsData as $optionData) { + $option = $this->optionFactory->create(['data' => $optionData]); + $options[] = $option; + } + + /** @var ProductAttributeInterface $attribute */ + $attribute = $this->attributeFactory->create(['data' => $attributeData]); + $attribute->setOptions($options); + $attribute->setNote('auto'); + + $productAttribute = $this->attributeRepository->save($attribute); + $attributeId = $productAttribute->getAttributeId(); + + //Associate Attribute to Attribute Set + $this->attributeManagement->assign($attributeSetId, $attributeGroupId, $attributeId, $sortOrder); + } + + return $this->getFormattedAttributeSetData($attributeSetData); + } + + /** + * Return formatted attribute set data + * + * @param array $attributeSetData + * @return array + */ + private function getFormattedAttributeSetData($attributeSetData) + { + $attributesData = array_key_exists(0, $attributeSetData['attributes']['attribute']) + ? $attributeSetData['attributes']['attribute'] : [$attributeSetData['attributes']['attribute']]; + $attributes = []; + foreach ($attributesData as $attributeData) { + $optionsData = array_key_exists(0, $attributeData['options']['option']) + ? $attributeData['options']['option'] : [$attributeData['options']['option']]; + $optionsData = array_map(function ($option) { + return $option['label']; + }, $optionsData); + $attributes[] = [ + 'name' => $attributeData['attribute_code'], + 'values' => $optionsData + ]; + } + + return [ + 'name' => $attributeSetData['name'], + 'attributes' => $attributes + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php b/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php new file mode 100644 index 0000000000000..7274f545feb9e --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php @@ -0,0 +1,95 @@ + 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 0, + 'attribute_code' => 'attribute_', + 'backend_type' => '', + 'is_searchable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'frontend_label' => 'Attribute ', + 'frontend_input' => 'select', + ]; + + /** + * Generate Data for Fixture to create an Attribute Set with specified pattern. + * + * @param string $name + * @param int $attributesPerSet + * @param int $optionsPerAttribute + * @param callable $attributePattern callback in f($index, $attributeData) format + * @return array + */ + public function generateAttributeSet( + $name, + $attributesPerSet, + $optionsPerAttribute, + $attributePattern = null + ) { + $attributeSet = [ + 'name' => $name, + 'attributes' => [] + ]; + for ($index = 1; $index <= $attributesPerSet; $index++) { + $attributeData = $this->generateAttribute( + $index, + $optionsPerAttribute + ); + if (is_callable($attributePattern)) { + $attributeData = $attributePattern($index, $attributeData); + } + $attributeSet['attributes']['attribute'][] = $attributeData; + } + + return $attributeSet; + } + + /** + * Generate Attributes for Set. + * + * @param int $index + * @param int $optionsPerAttribute + * @return array + */ + private function generateAttribute($index, $optionsPerAttribute) + { + $attribute = $this->attributePattern; // copy pattern + $attribute['attribute_code'] = $attribute['attribute_code'] . $index; + $attribute['frontend_label'] = $attribute['frontend_label'] . $index; + $attribute['options'] = ['option' => $this->generateOptions($optionsPerAttribute)]; + $attribute['default_option'] = $attribute['options']['option'][0]['label']; + return $attribute; + } + + /** + * Generate Options for Attribute. + * + * @param int $optionsPerAttribute + * @return array + */ + private function generateOptions($optionsPerAttribute) + { + $options = []; + for ($index = 1; $index <= $optionsPerAttribute; $index++) { + $options[] = [ + 'label' => 'option ' . $index, + 'value' => 'option_' . $index + ]; + } + + return $options; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php b/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php new file mode 100644 index 0000000000000..5210f5360027a --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/AttributeSet/SwatchesGenerator.php @@ -0,0 +1,162 @@ +filesystem = $filesystem; + $this->mediaConfig = $config; + $this->swatchHelper = $swatchHelper; + } + + /** + * Generates data for Swatch Attribute of the required type + * + * @param int $optionCount + * @param string $data + * @param string $type + * @return array + */ + public function generateSwatchData($optionCount, $data, $type) + { + if ($type === null) { + return []; + } + + $attribute['swatch_input_type'] = Swatch::SWATCH_INPUT_TYPE_VISUAL; + $attribute['swatchvisual']['value'] = array_reduce( + range(1, $optionCount), + function ($values, $index) use ($optionCount, $data, $type) { + if ($type === 'image') { + $values['option_' . $index] = $this->generateSwatchImage($data . $index); + } + if ($type === 'color') { + $values['option_' . $index] = $this->generateSwatchColor($index / $optionCount); + } + return $values; + }, + [] + ); + $attribute['optionvisual']['value'] = array_reduce( + range(1, $optionCount), + function ($values, $index) use ($optionCount) { + $values['option_' . $index] = ['option ' . $index]; + return $values; + }, + [] + ); + + return $attribute; + } + + /** + * Generate hex-coded color for Swatch Attribute based on provided index + * + * Colors will change gradually according to index value. + * + * @param int $index + * @return string + */ + private function generateSwatchColor($index) + { + return '#' . str_repeat(dechex(255 * $index), 3); + } + + /** + * Generate and save image for Swatch Attribute + * + * Image is generated with a set background color rgb(240, 240, 240), random foreground color, and pattern which + * is based on the binary representation of $data. + * + * @param string $data String value to be used for generation. + * @return string Path to the image file. + */ + private function generateSwatchImage($data) + { + $binaryData = ''; + $data = str_split(sha1($data), 2); + foreach ($data as $item) { + $binaryData .= base_convert($item, 16, 2); + } + $binaryData = str_split($binaryData, 1); + + $image = imagecreate(self::GENERATED_SWATCH_WIDTH, self::GENERATED_SWATCH_HEIGHT); + $bgColor = imagecolorallocate($image, 240, 240, 240); + $fgColor = imagecolorallocate($image, mt_rand(0, 230), mt_rand(0, 230), mt_rand(0, 230)); + $colors = [$fgColor, $bgColor]; + imagefilledrectangle($image, 0, 0, self::GENERATED_SWATCH_WIDTH, self::GENERATED_SWATCH_HEIGHT, $bgColor); + + for ($row = 10; $row < 100; $row += 18) { + for ($col = 0; $col < 90; $col += 18) { + next($binaryData); + imagefilledrectangle($image, $row, $col, $row + 18, $col + 18, $colors[current($binaryData)]); + } + } + + $mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $absolutePathToMedia = $mediaDirectory->getAbsolutePath($this->mediaConfig->getBaseTmpMediaPath()); + $relativePathToMedia = $mediaDirectory->getRelativePath($this->mediaConfig->getBaseTmpMediaPath()); + $mediaDirectory->create($relativePathToMedia); + + imagejpeg($image, $absolutePathToMedia . DIRECTORY_SEPARATOR . self::GENERATED_SWATCH_TMP_NAME, 100); + $imagePath = substr($this->swatchHelper->moveImageFromTmp(self::GENERATED_SWATCH_TMP_NAME), 1); + $this->swatchHelper->generateSwatchVariations($imagePath); + + return $imagePath; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php b/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php new file mode 100644 index 0000000000000..a555381438075 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php @@ -0,0 +1,99 @@ +attributeSetsFixture = $attributeSetsFixture; + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $attributeSets = $this->fixtureModel->getValue('attribute_sets', null); + if ($attributeSets !== null) { + foreach ($attributeSets['attribute_set'] as $attributeSetData) { + $this->attributeSetsFixture->createAttributeSet($attributeSetData); + } + } + + $attributeSetsCount = $this->fixtureModel->getValue('product_attribute_sets', null); + if ($attributeSetsCount !== null) { + for ($index = 1; $index <= $attributeSetsCount; $index++) { + $this->attributeSetsFixture->createAttributeSet( + $this->pattern->generateAttributeSet( + self::PRODUCT_SET_NAME . $index, + $this->fixtureModel->getValue('product_attribute_sets_attributes', 3), + $this->fixtureModel->getValue('product_attribute_sets_attributes_values', 3), + function ($attributeIndex, $attribute) use ($index) { + return array_replace_recursive( + $attribute, + [ + 'attribute_code' => "attribute_set{$index}_" . $attributeIndex, + ] + ); + } + ) + ); + } + } + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating attribute sets'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'attribute_sets' => 'Attribute Sets (Default)', + 'product_attribute_sets' => 'Attribute Sets (Extra)' + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php b/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php new file mode 100644 index 0000000000000..038026f097bf4 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php @@ -0,0 +1,261 @@ +{products amount} + * {bundle product options amount} + * {amount of simple products per each option} + * + * Products will be uniformly distributed per categories and websites + * If node "assign_entities_to_all_websites" from profile is set to "1" then products will be assigned to all websites + * + * @see setup/performance-toolkit/profiles/ce/small.xml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BundleProductsFixture extends Fixture +{ + /** + * Bundle sku pattern with entity number and suffix. Suffix equals "{options}-{variations_per_option}" + */ + const SKU_PATTERN = 'Bundle Product %s - %s'; + + /** + * @var int + */ + protected $priority = 42; + + /** + * @var \Magento\Setup\Model\FixtureGenerator\ProductGenerator + */ + private $productGenerator; + + /** + * @var \Magento\Setup\Model\FixtureGenerator\BundleProductGenerator + */ + private $bundleProductGenerator; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + */ + private $productCollectionFactory; + + /** + * @var int + */ + private $productStartIndex; + + /** + * @var ProductsAmountProvider + */ + private $productsAmountProvider; + + /** + * @var WebsiteCategoryProvider + */ + private $websiteCategoryProvider; + + /** + * @var PriceProvider + */ + private $priceProvider; + + /** + * @param FixtureModel $fixtureModel + * @param \Magento\Setup\Model\FixtureGenerator\ProductGenerator $productGenerator + * @param \Magento\Setup\Model\FixtureGenerator\BundleProductGenerator $bundleProductGenerator + * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory + * @param ProductsAmountProvider $productsAmountProvider + * @param WebsiteCategoryProvider $websiteCategoryProvider + * @param PriceProvider $priceProvider + */ + public function __construct( + FixtureModel $fixtureModel, + \Magento\Setup\Model\FixtureGenerator\ProductGenerator $productGenerator, + \Magento\Setup\Model\FixtureGenerator\BundleProductGenerator $bundleProductGenerator, + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, + ProductsAmountProvider $productsAmountProvider, + WebsiteCategoryProvider $websiteCategoryProvider, + PriceProvider $priceProvider + ) { + parent::__construct($fixtureModel); + $this->productGenerator = $productGenerator; + $this->bundleProductGenerator = $bundleProductGenerator; + $this->productCollectionFactory = $productCollectionFactory; + $this->productsAmountProvider = $productsAmountProvider; + $this->websiteCategoryProvider = $websiteCategoryProvider; + $this->priceProvider = $priceProvider; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function execute() + { + $bundlesAmount = $this->fixtureModel->getValue('bundle_products', 0); + $bundleOptions = $this->fixtureModel->getValue('bundle_products_options', 1); + $bundleProductsPerOption = $this->fixtureModel->getValue('bundle_products_variation', 10); + $bundleOptionSuffix = $bundleOptions . '-' . $bundleProductsPerOption; + $variationCount = $bundleOptions * $bundleProductsPerOption; + $bundlesAmount = $this->productsAmountProvider->getAmount( + $bundlesAmount, + $this->getBundleSkuPattern($bundleOptionSuffix) + ); + + if (!$bundlesAmount) { + return; + } + $variationSkuClosure = function ($productId, $entityNumber) use ($bundleOptionSuffix, $variationCount) { + $productIndex = $this->getBundleProductIndex($entityNumber, $variationCount); + $variationIndex = $this->getBundleVariationIndex($entityNumber, $variationCount) ; + + return sprintf($this->getBundleOptionItemSkuPattern($bundleOptionSuffix), $productIndex, $variationIndex); + }; + $fixtureMap = [ + 'name' => $variationSkuClosure, + 'sku' => $variationSkuClosure, + 'price' => function ($index, $entityNumber) { + return $this->priceProvider->getPrice($entityNumber); + }, + 'website_ids' => function ($index, $entityNumber) use ($variationCount) { + $configurableIndex = $this->getBundleProductIndex($entityNumber, $variationCount); + + return $this->websiteCategoryProvider->getWebsiteIds($configurableIndex); + }, + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + ]; + $this->productGenerator->generate($bundlesAmount * $bundleOptions * $bundleProductsPerOption, $fixtureMap); + + $optionPriceType = [ + LinkInterface::PRICE_TYPE_FIXED, + LinkInterface::PRICE_TYPE_PERCENT, + ]; + $priceTypeClosure = function ($index) use ($optionPriceType) { + return $optionPriceType[$index % count($optionPriceType)]; + }; + $skuClosure = function ($index, $entityNumber) use ($bundleOptionSuffix) { + return sprintf( + $this->getBundleSkuPattern($bundleOptionSuffix), + $entityNumber + $this->getNewProductStartIndex() + ); + }; + $fixtureMap = [ + '_bundle_options' => $bundleOptions, + '_bundle_products_per_option' => $bundleProductsPerOption, + '_bundle_variation_sku_pattern' => sprintf( + $this->getBundleOptionItemSkuPattern($bundleOptionSuffix), + $this->getNewProductStartIndex(), + '%s' + ), + 'type_id' => Type::TYPE_CODE, + 'name' => $skuClosure, + 'sku' => $skuClosure, + 'meta_title' => $skuClosure, + 'price' => function ($index) use ($priceTypeClosure) { + return $priceTypeClosure($index) === LinkInterface::PRICE_TYPE_PERCENT + ? mt_rand(10, 90) + : $this->priceProvider->getPrice($index); + }, + 'priceType' => $priceTypeClosure, + 'website_ids' => function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getWebsiteIds($entityNumber + $this->getNewProductStartIndex()); + }, + 'category_ids' => function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getCategoryId($entityNumber + $this->getNewProductStartIndex()); + }, + ]; + $this->bundleProductGenerator->generate($bundlesAmount, $fixtureMap); + } + + /** + * Get sku pattern for bundle product option item + * + * @param string $bundleOptionSuffix + * @return string + */ + private function getBundleOptionItemSkuPattern($bundleOptionSuffix) + { + return $this->getBundleSkuPattern($bundleOptionSuffix) . ' - option %s'; + } + + /** + * Get sku pattern for bundle product. Replace suffix pattern with passed value + * + * @param string $bundleOptionSuffix + * @return string + */ + private function getBundleSkuPattern($bundleOptionSuffix) + { + return sprintf(self::SKU_PATTERN, '%s', $bundleOptionSuffix); + } + + /** + * Get start index for product number generation + * + * @return int + */ + private function getNewProductStartIndex() + { + if (null === $this->productStartIndex) { + $this->productStartIndex = $this->productCollectionFactory->create() + ->addFieldToFilter('type_id', Type::TYPE_CODE) + ->getSize() + 1; + } + + return $this->productStartIndex; + } + + /** + * Get bundle product index number + * + * @param int $entityNumber + * @param int $variationCount + * @return float + */ + private function getBundleProductIndex($entityNumber, $variationCount) + { + return floor($entityNumber / $variationCount) + $this->getNewProductStartIndex(); + } + + /** + * Get bundle variation index number + * + * @param int $entityNumber + * @param int $variationCount + * @return float + */ + private function getBundleVariationIndex($entityNumber, $variationCount) + { + return $entityNumber % $variationCount + 1; + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating bundle products'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'bundle_products' => 'Bundle products', + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/CartPriceRulesFixture.php b/setup/src/Magento/Setup/Fixtures/CartPriceRulesFixture.php index 5e6a36d2f1d03..7647a6776e2a7 100755 --- a/setup/src/Magento/Setup/Fixtures/CartPriceRulesFixture.php +++ b/setup/src/Magento/Setup/Fixtures/CartPriceRulesFixture.php @@ -1,6 +1,6 @@ {amount of categories} + * {Nesting level of categories} + * + * If config "assign_entities_to_all_websites" set to "0" then all categories will be + * uniformly distributed per root categories, else all categories assigned to one root category */ class CategoriesFixture extends Fixture { + /** + * @var StoreManager + */ + private $storeManager; + + /** + * @var CategoryFactory + */ + private $categoryFactory; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var int + */ + private $firstLevelCategoryIndex; + + /** + * @var array + */ + private $rootCategoriesIds; + + /** + * @var int + */ + private $categoriesNumber; + + /** + * @var int + */ + private $maxNestingLevel; + + /** + * CategoriesFixture constructor. + * @param FixtureModel $fixtureModel + * @param StoreManager $storeManager + * @param CategoryFactory $categoryFactory + * @param CollectionFactory $collectionFactory + */ + public function __construct( + FixtureModel $fixtureModel, + StoreManager $storeManager, + CategoryFactory $categoryFactory, + CollectionFactory $collectionFactory + ) { + parent::__construct($fixtureModel); + $this->storeManager = $storeManager; + $this->categoryFactory = $categoryFactory; + $this->collectionFactory = $collectionFactory; + } + /** * @var int */ @@ -21,61 +86,154 @@ class CategoriesFixture extends Fixture */ public function execute() { - $categoriesNumber = $this->fixtureModel->getValue('categories', 0); - if (!$categoriesNumber) { + $this->categoriesNumber = $this->getCategoriesAmount(); + if (!$this->categoriesNumber) { return; } - $maxNestingLevel = $this->fixtureModel->getValue('categories_nesting_level', 3); - $this->fixtureModel->resetObjectManager(); - - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->create(\Magento\Catalog\Model\Category::class); - - $storeGroups = $storeManager->getGroups(); - $i = 0; - foreach ($storeGroups as $storeGroup) { - $parentCategoryId[$i] = $defaultParentCategoryId[$i] = $storeGroup->getRootCategoryId(); - $nestingLevel[$i] = 1; - $nestingPath[$i] = "1/$parentCategoryId[$i]"; - $categoryPath[$i] = ''; - $i++; + $this->maxNestingLevel = $this->fixtureModel->getValue('categories_nesting_level', 3); + + $categoriesNumberOnLevel = abs(ceil(pow($this->categoriesNumber, 1 / $this->maxNestingLevel) - 2)); + foreach ($this->getRootCategoriesIds() as $parentCategoryId) { + $category = $this->categoryFactory->create(); + $category->load($parentCategoryId); + // Need for generation url rewrites per all category store view + $category->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + $categoryIndex = 1; + $this->generateCategories( + $category, + $categoriesNumberOnLevel, + 1, + $categoryIndex + ); } - $groupNumber = 0; - $categoryIndex = 1; - - while ($categoryIndex <= $categoriesNumber) { - $category->setId(null) - ->setUrlKey(null) - ->setUrlPath(null) - ->setName("Category $categoryIndex") - ->setParentId($parentCategoryId[$groupNumber]) - ->setPath($nestingPath[$groupNumber]) - ->setLevel($nestingLevel[$groupNumber] + 1) - ->setAvailableSortBy('name') - ->setIsAnchor(false) - ->setDefaultSortBy('name') - ->setIsActive(true) - ->save(); - $categoryIndex++; - $categoryPath[$groupNumber] .= '/' . $category->getName(); - - if ($nestingLevel[$groupNumber]++ == $maxNestingLevel) { - $nestingLevel[$groupNumber] = 1; - $parentCategoryId[$groupNumber] = $defaultParentCategoryId[$groupNumber]; - $nestingPath[$groupNumber] = '1'; - $categoryPath[$groupNumber] = ''; - } else { - $parentCategoryId[$groupNumber] = $category->getId(); + } + + /** + * Generate categories + * + * @param Category $parentCategory + * @param int $categoriesNumberOnLevel + * @param int $nestingLevel + * @param int $categoryIndex + * @return void + */ + private function generateCategories( + Category $parentCategory, + $categoriesNumberOnLevel, + $nestingLevel, + &$categoryIndex + ) { + $maxCategoriesNumberOnLevel = $nestingLevel === 1 ? $this->categoriesNumber : $categoriesNumberOnLevel; + for ($i = 0; $i < $maxCategoriesNumberOnLevel && $categoryIndex <= $this->categoriesNumber; $i++) { + try { + $category = clone $parentCategory; + $category->setId(null) + ->setUrlKey(null) + ->setUrlPath(null) + ->setName($this->getCategoryName($parentCategory, $nestingLevel, $i)) + ->setParentId($parentCategory->getId()) + ->setLevel($parentCategory->getLevel() + 1) + ->setAvailableSortBy('name') + ->setIsAnchor($nestingLevel <= 2) + ->setDefaultSortBy('name') + ->setIsActive(true); + $category->save(); + $categoryIndex++; + if ($nestingLevel < $this->maxNestingLevel) { + $this->generateCategories( + $category, + $categoriesNumberOnLevel, + $nestingLevel + 1, + $categoryIndex + ); + } + } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { + $categoryIndex++; + continue; + } catch (\Magento\Framework\DB\Adapter\DuplicateException $e) { + $categoryIndex++; + continue; } - $nestingPath[$groupNumber] .= "/$parentCategoryId[$groupNumber]"; + } + } + + /** + * Get category name based on parent category and current level + * + * @param Category $parentCategory + * @param int $nestingLevel + * @param int $index + * @return string + */ + private function getCategoryName($parentCategory, $nestingLevel, $index) + { + $categoryNameSuffix = $nestingLevel === 1 ? $this->getFirstLevelCategoryIndex() + $index : $index + 1; + return ($nestingLevel === 1 ? $this->getCategoryPrefix() . ' ' : $parentCategory->getName() . '.') + . $categoryNameSuffix; + } - $groupNumber++; - if ($groupNumber == count($defaultParentCategoryId)) { - $groupNumber = 0; + /** + * Get ids of root categories + * + * @return int[] + */ + private function getRootCategoriesIds() + { + if (null === $this->rootCategoriesIds) { + $this->rootCategoriesIds = []; + foreach ($this->storeManager->getGroups() as $storeGroup) { + $this->rootCategoriesIds[] = $storeGroup->getRootCategoryId(); + // in this case root category will be the same for all store groups + if ((bool)$this->fixtureModel->getValue('assign_entities_to_all_websites', false)) { + break; + } } } + + return $this->rootCategoriesIds; + } + + /** + * Get categories amount for generation + * + * @return int + */ + private function getCategoriesAmount() + { + $categoriesAmount = $this->collectionFactory->create()->getSize(); + $rootCategories = count($this->getRootCategoriesIds()); + $categoriesNumber = $this->fixtureModel->getValue('categories', 0) - ($categoriesAmount - $rootCategories - 1); + + return max( + 0, + ceil($categoriesNumber / $rootCategories) + ); + } + + /** + * Get next category index, which will be used as index of first-level category + * + * @return int + */ + private function getFirstLevelCategoryIndex() + { + if (null === $this->firstLevelCategoryIndex) { + $this->firstLevelCategoryIndex = $this->collectionFactory->create() + ->addFieldToFilter('level', 2) + ->getSize() + 1; + } + + return $this->firstLevelCategoryIndex; + } + + /** + * Get Category name prefix + * + * @return string + */ + private function getCategoryPrefix() + { + return 'Category'; } /** diff --git a/setup/src/Magento/Setup/Fixtures/CategoryResolver.php b/setup/src/Magento/Setup/Fixtures/CategoryResolver.php new file mode 100644 index 0000000000000..037aef68dfb5b --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/CategoryResolver.php @@ -0,0 +1,104 @@ +storeManager = $storeManager; + $this->categoryFactory = $categoryFactory; + $this->collectionFactory = $collectionFactory; + $this->categoryRepository = $categoryRepository; + } + + /** + * Get category id + * + * @param int $websiteId + * @param string $categoryName + * @return int + */ + public function getCategory($websiteId, $categoryName) + { + $categoryKey = $websiteId . $categoryName; + + if (!isset($this->categories[$categoryKey])) { + $website = $this->storeManager->getWebsite($websiteId); + $rootCategoryId = $website->getDefaultGroup()->getRootCategoryId(); + $website->getDefaultGroup()->getStoreId(); + $category = $this->collectionFactory->create() + ->addFieldToFilter('parent_id', $rootCategoryId) + ->addFieldToFilter('name', $categoryName) + ->fetchItem(); + if ($category && $category->getId()) { + $this->categories[$categoryKey] = $category->getId(); + } else { + $category = $this->categoryFactory->create( + [ + 'data' => [ + 'parent_id' => $rootCategoryId, + 'name' => $categoryName, + 'position' => 1, + 'is_active' => true, + 'available_sort_by' => ['position', 'name'], + 'url_key' => $categoryName . '-' . $websiteId + ] + ] + ); + $category = $this->categoryRepository->save($category); + $this->categories[$categoryKey] = $category->getId(); + } + } + + return $this->categories[$categoryKey]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/ConfigsApplyFixture.php b/setup/src/Magento/Setup/Fixtures/ConfigsApplyFixture.php index bc8362890b638..df86ec12164eb 100644 --- a/setup/src/Magento/Setup/Fixtures/ConfigsApplyFixture.php +++ b/setup/src/Magento/Setup/Fixtures/ConfigsApplyFixture.php @@ -1,6 +1,6 @@ {products amount} + * + * 2.1 Generate products based on existing attribute set: + * + * + * {Existing attribute set name} + * {Configurable sku pattern with %s} + * {Amount of configurable products} + * [{Category Name}] By default category name from CategoriesFixture will be used + * color|image Type of Swatch attribute + * + * + * + * 2.2 Generate products based on dynamically created attribute set with specified amount of attributes and options + * + * + * {Amount of attributes in configurable product} + * {Amount of options per attribute} + * {Configurable sku pattern with %s} + * {Amount of configurable products} + * [{Category Name}] By default category name from CategoriesFixture will be used + * color|image Type of Swatch attribute + * + * + * + * Products will be uniformly distributed per categories and websites + * If node "assign_entities_to_all_websites" from profile is set to "1" then products will be assigned to all websites + * + * @see setup/performance-toolkit/profiles/ce/small.xml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ConfigurableProductsFixture extends Fixture { @@ -19,498 +60,785 @@ class ConfigurableProductsFixture extends Fixture */ protected $priority = 50; - //@codingStandardsIgnoreStart /** - * Get CSV template headers + * @var array + */ + private $searchConfig; + + /** + * @var DataGenerator + */ + private $dataGenerator; + + /** + * @var AttributeSet\AttributeSetFixture + */ + private $attributeSetsFixture; + + /** + * @var AttributeSet\Pattern + */ + private $attributePattern; + + /** + * @var ProductGenerator + */ + private $productGenerator; + + /** + * @var CollectionFactory + */ + private $attributeCollectionFactory; + + /** + * @var ConfigurableProductGenerator + */ + private $configurableProductGenerator; + + /** + * @var ProductCollectionFactory + */ + private $productCollectionFactory; + + /** + * @var int + */ + private $productStartIndex; + + /** + * @var ProductsAmountProvider + */ + private $productsAmountProvider; + + /** + * @var CategoryResolver + */ + private $categoryResolver; + + /** + * @var WebsiteCategoryProvider + */ + private $websiteCategoryProvider; + + /** + * @var PriceProvider + */ + private $priceProvider; + + /** + * @var \Magento\Setup\Fixtures\AttributeSet\SwatchesGenerator + */ + private $swatchesGenerator; + + /** + * ConfigurableProductsFixture constructor. + * @param FixtureModel $fixtureModel + * @param AttributeSet\AttributeSetFixture $attributeSetsFixture + * @param AttributeSet\Pattern $attributePattern + * @param ProductGenerator $productGenerator + * @param CollectionFactory $attributeCollectionFactory + * @param ConfigurableProductGenerator $configurableProductGenerator + * @param ProductCollectionFactory $collectionFactory + * @param ProductsAmountProvider $productsAmountProvider + * @param CategoryResolver $categoryResolver + * @param WebsiteCategoryProvider $websiteCategoryProvider + * @param PriceProvider $priceProvider + * @param \Magento\Setup\Fixtures\AttributeSet\SwatchesGenerator $swatchesGenerator + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + FixtureModel $fixtureModel, + \Magento\Setup\Fixtures\AttributeSet\AttributeSetFixture $attributeSetsFixture, + \Magento\Setup\Fixtures\AttributeSet\Pattern $attributePattern, + ProductGenerator $productGenerator, + CollectionFactory $attributeCollectionFactory, + ConfigurableProductGenerator $configurableProductGenerator, + ProductCollectionFactory $collectionFactory, + ProductsAmountProvider $productsAmountProvider, + CategoryResolver $categoryResolver, + WebsiteCategoryProvider $websiteCategoryProvider, + PriceProvider $priceProvider, + \Magento\Setup\Fixtures\AttributeSet\SwatchesGenerator $swatchesGenerator + ) { + parent::__construct($fixtureModel); + $this->attributeSetsFixture = $attributeSetsFixture; + $this->attributePattern = $attributePattern; + $this->productGenerator = $productGenerator; + $this->attributeCollectionFactory = $attributeCollectionFactory; + $this->configurableProductGenerator = $configurableProductGenerator; + $this->productCollectionFactory = $collectionFactory; + $this->productsAmountProvider = $productsAmountProvider; + $this->categoryResolver = $categoryResolver; + $this->websiteCategoryProvider = $websiteCategoryProvider; + $this->priceProvider = $priceProvider; + $this->swatchesGenerator = $swatchesGenerator; + } + + /** + * {@inheritdoc} * @SuppressWarnings(PHPMD) - * @return array */ - protected function getHeaders() + public function execute() { - return [ - 'sku', - 'store_view_code', - 'attribute_set_code', - 'product_type', - 'categories', - 'product_websites', - 'color', - 'configurable_variation', - 'cost', - 'country_of_manufacture', - 'created_at', - 'custom_design', - 'custom_design_from', - 'custom_design_to', - 'custom_layout_update', - 'description', - 'enable_googlecheckout', - 'gallery', - 'gift_message_available', - 'gift_wrapping_available', - 'gift_wrapping_price', - 'has_options', - 'image', - 'image_label', - 'is_returnable', - 'manufacturer', - 'meta_description', - 'meta_keyword', - 'meta_title', - 'minimal_price', - 'msrp', - 'msrp_display_actual_price_type', - 'name', - 'news_from_date', - 'news_to_date', - 'options_container', - 'page_layout', - 'price', - 'quantity_and_stock_status', - 'related_tgtr_position_behavior', - 'related_tgtr_position_limit', - 'required_options', - 'short_description', - 'small_image', - 'small_image_label', - 'special_from_date', - 'special_price', - 'special_to_date', - 'product_online', - 'tax_class_name', - 'thumbnail', - 'thumbnail_label', - 'updated_at', - 'upsell_tgtr_position_behavior', - 'upsell_tgtr_position_limit', - 'url_key', - 'url_path', - 'variations', - 'variations_1382710717', - 'variations_1382710773', - 'variations_1382710861', - 'visibility', - 'weight', - 'qty', - 'min_qty', - 'use_config_min_qty', - 'is_qty_decimal', - 'backorders', - 'use_config_backorders', - 'min_sale_qty', - 'use_config_min_sale_qty', - 'max_sale_qty', - 'use_config_max_sale_qty', - 'is_in_stock', - 'notify_stock_qty', - 'use_config_notify_stock_qty', - 'manage_stock', - 'use_config_manage_stock', - 'use_config_qty_increments', - 'qty_increments', - 'use_config_enable_qty_inc', - 'enable_qty_increments', - 'is_decimal_divided', - '_related_sku', - '_related_position', - '_crosssell_sku', - '_crosssell_position', - '_upsell_sku', - '_upsell_position', - '_associated_sku', - '_associated_default_qty', - '_associated_position', - '_tier_price_website', - '_tier_price_customer_group', - '_tier_price_qty', - '_tier_price_price', - '_media_attribute_id', - '_media_image', - '_media_label', - '_media_position', - '_media_is_disabled', - '_super_products_sku', - '_super_attribute_code', - '_super_attribute_option', - 'configurable_variations', - ]; + if (!$this->fixtureModel->getValue('configurable_products', [])) { + return; + } + $simpleProductsCount = $this->fixtureModel->getValue('simple_products', 0); + $maxAmountOfWordsDescription = $this->getSearchConfigValue('max_amount_of_words_description'); + $maxAmountOfWordsShortDescription = $this->getSearchConfigValue('max_amount_of_words_short_description'); + $minAmountOfWordsDescription = $this->getSearchConfigValue('min_amount_of_words_description'); + $minAmountOfWordsShortDescription = $this->getSearchConfigValue('min_amount_of_words_short_description'); + + foreach ($this->getConfigurableProductConfig() as $configurableConfig) { + $configurableSku = $configurableConfig['sku']; + $productAmount = $this->productsAmountProvider->getAmount( + $configurableConfig['products'], + $configurableSku + ); + if (!$productAmount) { + continue; + } + $searchTerms = $this->getSearchTerms(); + $shortDescriptionClosure = $this->getDescriptionClosure( + $searchTerms, + $simpleProductsCount, + $productAmount, + $maxAmountOfWordsShortDescription, + $minAmountOfWordsShortDescription, + 'shortDescription' + ); + $descriptionClosure = $this->getDescriptionClosure( + $searchTerms, + $simpleProductsCount, + $productAmount, + $maxAmountOfWordsDescription, + $minAmountOfWordsDescription, + 'description' + ); + $variationCount = $configurableConfig['variationCount']; + $attributeSet = $configurableConfig['attributeSet']; + $variationSkuClosure = function ($productId, $entityNumber) use ($configurableSku, $variationCount) { + $variationIndex = $this->getConfigurableVariationIndex($entityNumber, $variationCount); + $productId = $this->getConfigurableProductIndex($entityNumber, $variationCount); + + return sprintf($this->getConfigurableOptionSkuPattern($configurableSku), $productId, $variationIndex); + }; + $fixture = [ + 'name' => $variationSkuClosure, + 'sku' => $variationSkuClosure, + 'price' => function ($index, $entityNumber) { + return $this->priceProvider->getPrice($entityNumber); + }, + 'website_ids' => function ($index, $entityNumber) use ($variationCount) { + $configurableIndex = $this->getConfigurableProductIndex($entityNumber, $variationCount); + + return $this->websiteCategoryProvider->getWebsiteIds($configurableIndex); + }, + 'attribute_set_id' => $attributeSet['id'], + 'additional_attributes' => $this->getAdditionalAttributesClosure( + $attributeSet['attributes'], + $variationCount + ), + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + ]; + $this->productGenerator->generate($productAmount * $variationCount, $fixture); + + $skuClosure = function ($productId, $entityNumber) use ($configurableSku) { + return sprintf($configurableSku, $entityNumber + $this->getNewProductStartIndex()); + }; + $fixture = [ + '_variation_sku_pattern' => $this->getFirstVariationSkuPattern($configurableConfig), + '_attributes_count' => count($attributeSet['attributes']), + '_variation_count' => $variationCount, + '_attributes' => $attributeSet['attributes'], + 'type_id' => Configurable::TYPE_CODE, + 'name' => $skuClosure, + 'sku' => $skuClosure, + 'description' => $descriptionClosure, + 'short_description' => $shortDescriptionClosure, + 'attribute_set_id' => $attributeSet['id'], + 'website_ids' => $this->getConfigurableWebsiteIdsClosure(), + 'category_ids' => $configurableConfig['category'], + 'meta_keyword' => $skuClosure, + 'meta_title' => $skuClosure, + ]; + + $this->configurableProductGenerator->generate($productAmount, $fixture); + } } /** - * @param callable $productCategory - * @param callable $productWebsite - * @param string $variation - * @param string $suffix + * @return \Closure + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + private function getConfigurableWebsiteIdsClosure() + { + return function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getWebsiteIds($entityNumber + $this->getNewProductStartIndex()); + }; + } + + /** + * Get product distribution per attribute sets for default attribute sets + * + * @param array $defaultAttributeSets + * @param int $configurableProductsCount * @return array - * @SuppressWarnings(PHPMD) */ - private function generateConfigurableProduct($productCategory, $productWebsite, $variation, $suffix) + private function getDefaultAttributeSetsConfig(array $defaultAttributeSets, $configurableProductsCount) { - return [ - 'sku' => 'Configurable Product %s' . $suffix, - 'store_view_code' => '', - 'attribute_set_code' => 'Default', - 'product_type' => 'configurable', - 'categories' => $productCategory, - 'product_websites' => $productWebsite, - 'color' => '', - 'configurable_variation' => '', - 'cost' => '', - 'country_of_manufacture' => '', - 'created_at' => '2013-10-25 15:12:39', - 'custom_design' => '', - 'custom_design_from' => '', - 'custom_design_to' => '', - 'custom_layout_update' => '', - 'description' => '

    Configurable product description %s

    ', - 'enable_googlecheckout' => '1', - 'gallery' => '', - 'gift_message_available' => '', - 'gift_wrapping_available' => '', - 'gift_wrapping_price' => '', - 'has_options' => '1', - 'image' => '', - 'image_label' => '', - 'is_returnable' => 'no', - 'manufacturer' => '', - 'meta_description' => 'Configurable Product %s

    Configurable product description %s

    ', - 'meta_keyword' => 'Configurable Product %s', - 'meta_title' => 'Configurable Product %s', - 'minimal_price' => '', - 'msrp' => '', - 'msrp_display_actual_price_type' => 'Use config', - 'name' => 'Configurable Product %s' . $suffix, - 'news_from_date' => '', - 'news_to_date' => '', - 'options_container' => 'Block after Info Column', - 'page_layout' => '', - 'price' => '10', - 'quantity_and_stock_status' => 'In Stock', - 'related_tgtr_position_behavior' => '', - 'related_tgtr_position_limit' => '', - 'required_options' => '1', - 'short_description' => '', - 'small_image' => '', - 'small_image_label' => '', - 'special_from_date' => '', - 'special_price' => '', - 'special_to_date' => '', - 'product_online' => '1', - 'tax_class_name' => 'Taxable Goods', - 'thumbnail' => '', - 'thumbnail_label' => '', - 'updated_at' => '2013-10-25 15:12:39', - 'upsell_tgtr_position_behavior' => '', - 'upsell_tgtr_position_limit' => '', - 'url_key' => "configurable-product-%s{$suffix}", - 'url_path' => "configurable-product-%s{$suffix}", - 'visibility' => 'Catalog, Search', - 'weight' => '', - 'qty' => 333, - 'min_qty' => '0.0000', - 'use_config_min_qty' => '1', - 'is_qty_decimal' => '0', - 'backorders' => '0', - 'use_config_backorders' => '1', - 'min_sale_qty' => '1.0000', - 'use_config_min_sale_qty' => '1', - 'max_sale_qty' => '0.0000', - 'use_config_max_sale_qty' => '1', - 'is_in_stock' => '1', - 'notify_stock_qty' => '', - 'use_config_notify_stock_qty' => '1', - 'manage_stock' => '1', - 'use_config_manage_stock' => '1', - 'use_config_qty_increments' => '1', - 'qty_increments' => '0.0000', - 'use_config_enable_qty_inc' => '1', - 'enable_qty_increments' => '0', - 'is_decimal_divided' => '0', - '_related_sku' => '', - '_related_position' => '', - '_crosssell_sku' => '', - '_crosssell_position' => '', - '_upsell_sku' => '', - '_upsell_position' => '', - '_associated_sku' => '', - '_associated_default_qty' => '', - '_associated_position' => '', - '_tier_price_website' => '', - '_tier_price_customer_group' => '', - '_tier_price_qty' => '', - '_tier_price_price' => '', - '_media_attribute_id' => '', - '_media_image' => '', - '_media_label' => '', - '_media_position' => '', - '_media_is_disabled' => '', - 'configurable_variations' => $variation, - ]; + $attributeSetClosure = function ($index) use ($defaultAttributeSets) { + $attributeSetAmount = count(array_keys($defaultAttributeSets)); + mt_srand($index); + + return $attributeSetAmount > ($index - 1) % (int)$this->fixtureModel->getValue('categories', 30) + ? array_keys($defaultAttributeSets)[mt_rand(0, $attributeSetAmount - 1)] + : 'Default'; + }; + $productsPerSet = []; + for ($i = 1; $i <= $configurableProductsCount; $i++) { + $attributeSet = $attributeSetClosure($i); + if (!isset($productsPerSet[$attributeSet])) { + $productsPerSet[$attributeSet] = 0; + } + $productsPerSet[$attributeSet]++; + } + $configurableConfig = []; + foreach ($defaultAttributeSets as $attributeSetName => $attributeSet) { + $skuSuffix = $attributeSetName === 'Default' ? '' : ' - ' . $attributeSetName; + $configurableConfig[] = [ + 'attributeSet' => $attributeSetName, + 'products' => $productsPerSet[$attributeSetName], + 'sku' => 'Configurable Product %s' . $skuSuffix, + ]; + } + + return $configurableConfig; } /** - * Get CSV template rows + * Get sku pattern in format "{configurable-sku}{configurable-index}-option 1" for get associated product ids * - * @param Closure|mixed $productCategory - * @param Closure|mixed $productWebsite + * @param array $configurableConfig + * @see \Magento\Setup\Model\FixtureGenerator\ConfigurableProductTemplateGenerator + * @return string + */ + private function getFirstVariationSkuPattern($configurableConfig) + { + $productIndex = $this->getConfigurableProductIndex(0, $configurableConfig['variationCount']); + + return sprintf($this->getConfigurableOptionSkuPattern($configurableConfig['sku']), $productIndex, 1); + } + + /** + * Get start product index which used in product name, sku, url generation * - * @SuppressWarnings(PHPMD) + * @return int + */ + private function getNewProductStartIndex() + { + if (null === $this->productStartIndex) { + $this->productStartIndex = $this->productCollectionFactory->create() + ->addFieldToFilter('type_id', Configurable::TYPE_CODE) + ->getSize() + 1; + } + + return $this->productStartIndex; + } + + /** + * Get configurable product index number + * + * @param int $entityNumber + * @param int $variationCount + * @return float + */ + private function getConfigurableProductIndex($entityNumber, $variationCount) + { + return floor($entityNumber / $variationCount) + $this->getNewProductStartIndex(); + } + + /** + * Get configurable variation index number + * + * @param int $entityNumber + * @param int $variationCount + * @return float + */ + private function getConfigurableVariationIndex($entityNumber, $variationCount) + { + return $entityNumber % $variationCount + 1; + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating configurable products'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function printInfo(OutputInterface $output) + { + if (!$this->fixtureModel->getValue('configurable_products', [])) { + return ; + } + $configurableProductConfig = $this->getConfigurableProductConfig(); + $generalAmount = array_sum(array_map( + function ($item) { + return $item['products']; + }, + $configurableProductConfig + )); + $output->writeln(sprintf(' |- Configurable products: %s', $generalAmount)); + foreach ($configurableProductConfig as $config) { + $output->writeln( + sprintf( + ' |--- %s products for attribute set "%s"', + $config['products'], + $config['attributeSet']['name'] + ) + ); + } + } + + /** + * Gen default attribute sets with attributes + * @see config/attributeSets.xml * * @return array */ - protected function getRows($productCategory, $productWebsite, $optionsNumber, $suffix = '') + private function getDefaultAttributeSetsWithAttributes() { - $data = []; - $variation = []; - for ($i = 1; $i <= $optionsNumber; $i++) { - $productData = [ - 'sku' => "Configurable Product %s-option {$i}{$suffix}", - 'store_view_code' => '', - 'attribute_set_code' => 'Default', - 'product_type' => 'simple', - 'categories' => $productCategory, - 'product_websites' => $productWebsite, - 'color' => '', - 'configurable_variation' => "option {$i}", - 'cost' => '', - 'country_of_manufacture' => '', - 'created_at' => '2013-10-25 15:12:32', - 'custom_design' => '', - 'custom_design_from' => '', - 'custom_design_to' => '', - 'custom_layout_update' => '', - 'description' => '

    Configurable product description %s

    ', - 'enable_googlecheckout' => '1', - 'gallery' => '', - 'gift_message_available' => '', - 'gift_wrapping_available' => '', - 'gift_wrapping_price' => '', - 'has_options' => '0', - 'image' => '', - 'image_label' => '', - 'is_returnable' => 'no', - 'manufacturer' => '', - 'meta_description' => 'Configurable Product %s

    Configurable product description 1

    ', - 'meta_keyword' => 'Configurable Product 1', - 'meta_title' => 'Configurable Product %s', - 'minimal_price' => '', - 'msrp' => '', - 'msrp_display_actual_price_type' => 'Use config', - 'name' => "Configurable Product {$suffix}- %s-option {$i}", - 'news_from_date' => '', - 'news_to_date' => '', - 'options_container' => 'Block after Info Column', - 'page_layout' => '', - 'price' => function () { return mt_rand(1, 1000) / 10; }, - 'quantity_and_stock_status' => 'In Stock', - 'related_tgtr_position_behavior' => '', - 'related_tgtr_position_limit' => '', - 'required_options' => '0', - 'short_description' => '', - 'small_image' => '', - 'small_image_label' => '', - 'special_from_date' => '', - 'special_price' => '', - 'special_to_date' => '', - 'product_online' => '1', - 'tax_class_name' => 'Taxable Goods', - 'thumbnail' => '', - 'thumbnail_label' => '', - 'updated_at' => '2013-10-25 15:12:32', - 'upsell_tgtr_position_behavior' => '', - 'upsell_tgtr_position_limit' => '', - 'url_key' => "simple-of-configurable-product-{$suffix}-%s-option-{$i}", - 'url_path' => "simple-of-configurable-product-{$suffix}-%s-option-{$i}", - 'variations' => '', - 'variations_1382710717' => '', - 'variations_1382710773' => '', - 'variations_1382710861' => '', - 'visibility' => 'Not Visible Individually', - 'weight' => '1', - 'qty' => '111.0000', - 'min_qty' => '0.0000', - 'use_config_min_qty' => '1', - 'is_qty_decimal' => '0', - 'backorders' => '0', - 'use_config_backorders' => '1', - 'min_sale_qty' => '1.0000', - 'use_config_min_sale_qty' => '1', - 'max_sale_qty' => '0.0000', - 'use_config_max_sale_qty' => '1', - 'is_in_stock' => '1', - 'notify_stock_qty' => '', - 'use_config_notify_stock_qty' => '1', - 'manage_stock' => '1', - 'use_config_manage_stock' => '1', - 'use_config_qty_increments' => '1', - 'qty_increments' => '0.0000', - 'use_config_enable_qty_inc' => '1', - 'enable_qty_increments' => '0', - 'is_decimal_divided' => '0', - '_related_sku' => '', - '_related_position' => '', - '_crosssell_sku' => '', - '_crosssell_position' => '', - '_upsell_sku' => '', - '_upsell_position' => '', - '_associated_sku' => '', - '_associated_default_qty' => '', - '_associated_position' => '', - '_tier_price_website' => '', - '_tier_price_customer_group' => '', - '_tier_price_qty' => '', - '_tier_price_price' => '', - '_media_attribute_id' => '', - '_media_image' => '', - '_media_label' => '', - '_media_position' => '', - '_media_is_disabled' => '', - ]; + $attributeSets = $this->fixtureModel->getValue('attribute_sets', null); + $attributeSetData = []; - $variation[] = implode( - ',', + if ($attributeSets !== null && array_key_exists('attribute_set', $attributeSets)) { + foreach ($attributeSets['attribute_set'] as $attributeSet) { + $attributesData = array_key_exists(0, $attributeSet['attributes']['attribute']) + ? $attributeSet['attributes']['attribute'] : [$attributeSet['attributes']['attribute']]; + $attributes = []; + foreach ($attributesData as $attributeData) { + $values = []; + $optionsData = array_key_exists(0, $attributeData['options']['option']) + ? $attributeData['options']['option'] : [$attributeData['options']['option']]; + foreach ($optionsData as $optionData) { + $values[] = $optionData['label']; + } + + $attributes[] = ['name' => $attributeData['attribute_code'], 'values' => $values]; + } + $attributeSetData[$attributeSet['name']] = [ + 'name' => $attributeSet['name'], + 'attributes' => $attributes + ]; + } + } + + $attributeOptions = range(1, $this->getConfigurableProductsVariationsValue()); + array_walk( + $attributeOptions, + function (&$item, $key) { + $item = 'option ' . ($key + 1); + } + ); + $attributeSetData['Default'] = [ + 'name' => 'Default', + 'attributes' => [ [ - 'sku=' . $productData['sku'], - 'configurable_variation=' . $productData['configurable_variation'], + 'name' => 'configurable_variation', + 'values' => $attributeOptions ] + ] + ]; + + return $attributeSetData; + } + + /** + * Get configurable product configuration for generate products per attribute set + * + * @return array + * @throws ValidatorException + */ + private function getConfigurableProductConfig() + { + $defaultAttributeSets = $this->getDefaultAttributeSetsWithAttributes(); + $configurableProductConfig = $this->prepareConfigurableConfig($defaultAttributeSets); + + $configurableProductConfig = array_map(function ($config) { + return array_merge( + [ + 'attributeSet' => null, + 'attributes' => null, + 'options' => null, + 'sku' => null, + 'category' => null, + 'swatches' => null, + ], + $config + ); + }, $configurableProductConfig); + + $skuPull = []; + foreach ($configurableProductConfig as $i => &$config) { + $attributeSet = $config['attributeSet']; + $attributes = (int)$config['attributes']; + $options = (int)$config['options']; + if ($attributeSet && isset($defaultAttributeSets[$attributeSet])) { + // process default attribute sets + $attributeSet = $defaultAttributeSets[$attributeSet]; + $attributes = count($attributeSet['attributes']); + $options = count($attributeSet['attributes'][0]['values']); + } elseif ($attributes && $options) { + // process dynamic attribute sets + $attributeSet = $this->getAttributeSet($attributes, $options, $config['swatches']); + } + // do not process if any required option is missed + if (count(array_filter([$attributeSet, $attributes, $options])) !== 3) { + unset($configurableProductConfig[$i]); + continue; + } + + $config['sku'] = $this->getConfigurableSkuPattern($config, $attributeSet['name']); + $config['category'] = $this->getConfigurableCategory($config); + $config['attributeSet'] = $this->convertAttributesToDBFormat($attributeSet); + $config['attributes'] = $attributes; + $config['options'] = $options; + $config['variationCount'] = pow($options, $attributes); + $skuPull[] = $config['sku']; + } + + if (count($skuPull) !== count(array_unique($skuPull))) { + throw new ValidatorException( + __('Sku pattern for configurable product must be unique per attribute set') ); - $data[] = $productData; } - $data[] = $this->generateConfigurableProduct( - $productCategory, - $productWebsite, - implode('|', $variation), - $suffix - ); - return $data; + return $configurableProductConfig; } /** - * {@inheritdoc} + * Prepare configuration. If amount of configurable products set in profile then return predefined attribute sets + * else return configuration from profile + * + * @param array $defaultAttributeSets + * @return array + * @throws ValidatorException */ - public function execute() + private function prepareConfigurableConfig($defaultAttributeSets) { - $configurableCount = $this->fixtureModel->getValue('configurable_products', 0); - if (!$configurableCount) { - return; + $configurableConfigs = $this->fixtureModel->getValue('configurable_products', []); + $configurableConfigs = is_array($configurableConfigs) ? $configurableConfigs : (int)$configurableConfigs; + if (is_int($configurableConfigs)) { + $configurableConfigs = $this->getDefaultAttributeSetsConfig($defaultAttributeSets, $configurableConfigs); + } elseif (isset($configurableConfigs['config'])) { + if (!isset($configurableConfigs['config'][0])) { + // in case when one variation is passed + $configurableConfigs = [$configurableConfigs['config']]; + } else { + $configurableConfigs = $configurableConfigs['config']; + } + } else { + throw new ValidatorException(__('Configurable product config is invalid')); } - $this->fixtureModel->resetObjectManager(); - $result = $this->getCategoriesAndWebsites(); + return $configurableConfigs; + } - $result = array_values($result); + /** + * @param array $config + * @return \Closure + */ + private function getConfigurableCategory($config) + { + if (isset($config['category'])) { + return function ($index, $entityNumber) use ($config) { + $websiteClosure = $this->getConfigurableWebsiteIdsClosure(); + $websites = $websiteClosure($index, $entityNumber); - $productWebsite = function ($index) use ($result) { - return $result[$index % count($result)][0]; - }; - $productCategory = function ($index) use ($result) { - return $result[$index % count($result)][2] . '/' . $result[$index % count($result)][1]; - }; + return $this->categoryResolver->getCategory( + array_shift($websites), + $config['category'] + ); + }; + } else { + return function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getCategoryId($entityNumber); + }; + } + } - /** - * Create configurable products - */ - $pattern = new Pattern(); - $pattern->setHeaders($this->getHeaders()); - $pattern->setRowsSet( - $this->getRows( - $productCategory, - $productWebsite, - $this->fixtureModel->getValue('configurable_products_variation', 3) - ) - ); + /** + * @param array $config + * @param string $attributeSetName + * @return string + */ + private function getConfigurableSkuPattern($config, $attributeSetName) + { + $sku = isset($config['sku']) ? $config['sku'] : null; + if (!$sku) { + $sku = 'Product ' . $attributeSetName . ' %s'; + } + if (false === strpos($sku, '%s')) { + $sku .= ' %s'; + } - /** @var \Magento\ImportExport\Model\Import $import */ - $import = $this->fixtureModel->getObjectManager()->create( - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'catalog_product', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors', - ], - ] - ); + return $sku; + } + + /** + * @param int $attributes + * @param int $options + * @param string $swatches + * @return array + */ + private function getAttributeSet($attributes, $options, $swatches) + { + $attributeCode = 'configurable_attribute' . $attributes . '_' . $options . '_'; + $setName = 'Dynamic Attribute Set ' . $attributes . '-' . $options; - $source = $this->fixtureModel->getObjectManager()->create( - Generator::class, - ['rowPattern' => $pattern, 'count' => $configurableCount] + return $this->attributeSetsFixture->createAttributeSet( + $this->attributePattern->generateAttributeSet( + $setName, + $attributes, + $options, + function ($index, $attribute) use ($attributeCode, $options, $swatches) { + $data = [ + 'attribute_code' => $attributeCode . $index, + 'frontend_label' => 'Big Attribute ' . $attributeCode . $index, + ]; + return array_replace_recursive( + $attribute, + $data, + $this->swatchesGenerator->generateSwatchData($options, $attributeCode . $index, $swatches) + ); + } + ) ); - // it is not obvious, but the validateSource() will actually save import queue data to DB - if (!$import->validateSource($source)) { - throw new \Exception($import->getFormatedLogTrace()); + } + + /** + * @return array + */ + private function getSearchConfig() + { + if (!$this->searchConfig) { + $this->searchConfig = $this->fixtureModel->getValue('search_config', null); } - // this converts import queue into actual entities - if (!$import->importSource()) { - throw new \Exception($import->getFormatedLogTrace()); + return $this->searchConfig; + } + + /** + * @param string $name + * @return int|mixed + */ + private function getSearchConfigValue($name) + { + return $this->getSearchConfig() === null + ? 0 : ($this->getSearchConfig()[$name] === null ? 0 : $this->getSearchConfig()[$name]); + } + + /** + * @return array + */ + private function getSearchTerms() + { + $searchTerms = $this->fixtureModel->getValue('search_terms', null); + if ($searchTerms !== null) { + $searchTerms = array_key_exists(0, $searchTerms['search_term']) + ? $searchTerms['search_term'] : [$searchTerms['search_term']]; } + return $searchTerms; } - // @codingStandardsIgnoreEnd /** - * {@inheritdoc} + * Get configurable products variations value. + * + * @return int */ - public function getActionTitle() + private function getConfigurableProductsVariationsValue() { - return 'Generating configurable products'; + return $this->fixtureModel->getValue('configurable_products_variation', 3); } /** - * {@inheritdoc} + * Get additional attributes closure. + * + * @param array $attributes + * @param int $variationCount + * @return \Closure + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function introduceParamLabels() + private function getAdditionalAttributesClosure(array $attributes, $variationCount) { - return [ - 'configurable_products' => 'Configurable products', - ]; + return function ($attributeSetId, $index, $entityNumber) use ($attributes, $variationCount) { + + $variationIndex = $this->getConfigurableVariationIndex($entityNumber, $variationCount) - 1; + $attributeValues = []; + $optionsPerAttribute = count($attributes[0]['values']); + $variationIndex = $variationIndex % $variationCount; + + $variationsMatrix = $this->generateVariationsMatrix(count($attributes), $optionsPerAttribute); + if (isset($variationsMatrix[$variationIndex])) { + $tempProductData = []; + foreach ($variationsMatrix[$variationIndex] as $attributeIndex => $optionIndex) { + $attributeCode = $attributes[$attributeIndex]['name']; + $option = $attributes[$attributeIndex]['values'][$optionIndex]; + $tempProductData[$attributeCode] = $option; + } + return $tempProductData; + } + + return $attributeValues; + }; } /** + * Generates matrix of all possible variations. + * @param int $attributesPerSet + * @param int $optionsPerAttribute * @return array */ - private function getCategoriesAndWebsites() + private function generateVariationsMatrix($attributesPerSet, $optionsPerAttribute) { - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->get(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->create(\Magento\Catalog\Model\Category::class); + $variationsMatrix = null; + for ($i = 1; $i <= $attributesPerSet; $i++) { + $variationsMatrix[] = range(0, $optionsPerAttribute - 1); + } + return $this->generateVariations($variationsMatrix); + } + /** + * Build all possible variations based on attributes and options count. + * @param array|null $variationsMatrix + * @return array + */ + private function generateVariations($variationsMatrix) + { + if (!$variationsMatrix) { + return [[]]; + } + $set = array_shift($variationsMatrix); + $subset = $this->generateVariations($variationsMatrix); $result = []; - //Get all websites - $websites = $storeManager->getWebsites(); - foreach ($websites as $website) { - $websiteCode = $website->getCode(); - //Get all groups - $websiteGroups = $website->getGroups(); - foreach ($websiteGroups as $websiteGroup) { - $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); - $category->load($websiteGroupRootCategory); - $categoryResource = $category->getResource(); - $rootCategoryName = $category->getName(); - //Get all categories - $resultsCategories = $categoryResource->getAllChildren($category); - foreach ($resultsCategories as $resultsCategory) { - $category->load($resultsCategory); - $structure = explode('/', $category->getPath()); - $pathSize = count($structure); - if ($pathSize > 1) { - $path = []; - for ($i = 1; $i < $pathSize; $i++) { - $path[] = $category->load($structure[$i])->getName(); - } - array_shift($path); - $resultsCategoryName = implode('/', $path); - } else { - $resultsCategoryName = $category->getName(); - } - //Deleted root categories - if (trim($resultsCategoryName) != '') { - $result[$resultsCategory] = [$websiteCode, $resultsCategoryName, $rootCategoryName]; + foreach ($set as $value) { + foreach ($subset as $subValue) { + array_unshift($subValue, $value); + $result[] = $subValue; + } + } + return $result; + } + + /** + * Get configurable option sku pattern. + * + * @param string $skuPattern + * @return string + */ + private function getConfigurableOptionSkuPattern($skuPattern) + { + return $skuPattern . ' - option %s'; + } + + /** + * @param array|null $searchTerms + * @param int $simpleProductsCount + * @param int $configurableProductsCount + * @param int $maxAmountOfWordsDescription + * @param int $minAmountOfWordsDescription + * @param string $descriptionPrefix + * @return \Closure + */ + private function getDescriptionClosure( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $maxAmountOfWordsDescription, + $minAmountOfWordsDescription, + $descriptionPrefix = 'description' + ) { + if (null === $this->dataGenerator) { + $fileName = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'dictionary.csv'; + $this->dataGenerator = new DataGenerator(realpath($fileName)); + } + + return function ($index) use ( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $maxAmountOfWordsDescription, + $minAmountOfWordsDescription, + $descriptionPrefix + ) { + $count = $searchTerms === null + ? 0 + : round( + $searchTerms[$index % count($searchTerms)]['count'] * ( + $configurableProductsCount / ($simpleProductsCount + $configurableProductsCount) + ) + ); + mt_srand($index); + return $this->dataGenerator->generate( + $minAmountOfWordsDescription, + $maxAmountOfWordsDescription, + $descriptionPrefix . '-' . $index + ) . + ($index <= ($count * count($searchTerms)) ? ' ' . + $searchTerms[$index % count($searchTerms)]['term'] : ''); + }; + } + + /** + * Convert attribute set data to db format for use in simple product generation + * + * @param array $attributeSet + * @return array + */ + private function convertAttributesToDBFormat(array $attributeSet) + { + $attributeSetName = $attributeSet['name']; + $attributeSetId = null; + $attributes = []; + foreach ($attributeSet['attributes'] as $attributeData) { + $attributeCollection = $this->attributeCollectionFactory->create(); + + $attributeCollection->setAttributeSetFilterBySetName($attributeSetName, Product::ENTITY); + $attributeCollection->addFieldToFilter( + 'attribute_code', + $attributeData['name'] + ); + /** @var \Magento\Eav\Model\Entity\Attribute $attribute */ + foreach ($attributeCollection as $attribute) { + $attributeSetId = $attribute->getAttributeSetId(); + $values = []; + $options = $attribute->getOptions(); + foreach ($options ?: [] as $option) { + if ($option->getValue()) { + $values[] = $option->getValue(); } } + $attributes[] = + [ + 'name' => $attribute->getAttributeCode(), + 'id' => $attribute->getAttributeId(), + 'values' => $values + ]; } } - return $result; + + return ['id' => $attributeSetId, 'name' => $attributeSetName, 'attributes' => $attributes]; } } diff --git a/setup/src/Magento/Setup/Fixtures/CustomerGroupsFixture.php b/setup/src/Magento/Setup/Fixtures/CustomerGroupsFixture.php new file mode 100644 index 0000000000000..327f597b69930 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/CustomerGroupsFixture.php @@ -0,0 +1,92 @@ +groupCollectionFactory = $groupCollectionFactory; + $this->groupRepository = $groupRepository; + $this->groupFactory = $groupFactory; + } + + /** + * @inheritdoc + */ + public function execute() + { + $existingCustomerGroupsCount = $this->groupCollectionFactory->create()->getSize(); + $customerGroupsCount = $this->fixtureModel->getValue('customer_groups', 0); + if ($customerGroupsCount < 1) { + return; + } + + for ($i = $existingCustomerGroupsCount; $i < $customerGroupsCount; ++$i) { + $groupDataObject = $this->groupFactory->create(); + $groupDataObject + ->setCode('customer_group_' . $i) + ->setTaxClassId(self::DEFAULT_TAX_CLASS_ID); + $this->groupRepository->save($groupDataObject); + } + } + + /** + * @inheritdoc + */ + public function getActionTitle() + { + return 'Generating customer groups'; + } + + /** + * @inheritdoc + */ + public function introduceParamLabels() + { + return [ + 'customer_groups' => 'Customer groups' + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/CustomersFixture.php b/setup/src/Magento/Setup/Fixtures/CustomersFixture.php index ca29b4cf70447..c81795d87d980 100644 --- a/setup/src/Magento/Setup/Fixtures/CustomersFixture.php +++ b/setup/src/Magento/Setup/Fixtures/CustomersFixture.php @@ -1,111 +1,121 @@ {customers amount} + * Customers will have normal distribution on all available websites + * + * Each customer will have absolutely the same data + * except customer email, customer group and customer addresses + * + * @see \Magento\Setup\Model\FixtureGenerator\CustomerTemplateGenerator + * to view general customer data + * + * @see \Magento\Setup\Model\Customer\CustomerDataGenerator + * if you need dynamically change data per each customer + * + * @see \Magento\Setup\Model\Address\AddressDataGenerator + * if you need dynamically change address data per each customer + * + * @see setup/performance-toolkit/config/customerConfig.xml + * here you can change amount of addresses to be generated per each customer + * Supports the following format: + * + * {amount of addresses} + * + * + * @see setup/performance-toolkit/profiles/ce/small.xml */ class CustomersFixture extends Fixture { /** * @var int */ - protected $priority = 60; + protected $priority = 70; + + /** + * @var CustomerGenerator + */ + private $customerGenerator; + + /** + * @var CustomerDataGeneratorFactory + */ + private $customerDataGeneratorFactory; + + /** + * @var array + */ + private $defaultCustomerConfig = [ + 'addresses-count' => 2 + ]; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @param FixtureModel $fixtureModel + * @param CustomerGenerator $customerGenerator + * @param CustomerDataGeneratorFactory $customerDataGeneratorFactory + * @param CollectionFactory $collectionFactory + */ + public function __construct( + FixtureModel $fixtureModel, + CustomerGenerator $customerGenerator, + CustomerDataGeneratorFactory $customerDataGeneratorFactory, + CollectionFactory $collectionFactory + ) { + parent::__construct($fixtureModel); + + $this->customerGenerator = $customerGenerator; + $this->customerDataGeneratorFactory = $customerDataGeneratorFactory; + $this->collectionFactory = $collectionFactory; + } /** * {@inheritdoc} */ public function execute() { - $customersNumber = $this->fixtureModel->getValue('customers', 0); + $customersNumber = $this->getCustomersAmount(); if (!$customersNumber) { return; } - $this->fixtureModel->resetObjectManager(); - - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $defaultStoreView \Magento\Store\Model\Store */ - $defaultStoreView = $storeManager->getDefaultStoreView(); - $defaultStoreViewId = $defaultStoreView->getStoreId(); - $defaultStoreViewCode = $defaultStoreView->getCode(); - - $result = []; - //Get all websites - $websites = $storeManager->getWebsites(); - foreach ($websites as $website) { - $result[] = $website->getCode(); - } - $result = array_values($result); - - $productWebsite = function ($index) use ($result) { - return $result[$index % count($result)]; - }; - - $pattern = [ - 'email' => 'user_%s@example.com', - '_website' => $productWebsite, - '_store' => $defaultStoreViewCode, - 'confirmation' => null, - 'created_at' => '30-08-2012 17:43', - 'created_in' => 'Default', - 'default_billing' => '1', - 'default_shipping' => '1', - 'disable_auto_group_change' => '0', - 'dob' => '12-10-1991', - 'firstname' => 'Firstname', - 'gender' => 'Male', - 'group_id' => '1', - 'lastname' => 'Lastname', - 'middlename' => '', - 'password_hash' => '', - 'prefix' => null, - 'rp_token' => null, - 'rp_token_created_at' => null, - 'store_id' => $defaultStoreViewId, - 'suffix' => null, - 'taxvat' => null, - 'website_id' => '1', - 'password' => '123123q', - '_address_city' => 'Fayetteville', - '_address_company' => '', - '_address_country_id' => 'US', - '_address_fax' => '', - '_address_firstname' => 'Anthony', - '_address_lastname' => 'Nealy', - '_address_middlename' => '', - '_address_postcode' => '123123', - '_address_prefix' => '', - '_address_region' => 'Arkansas', - '_address_street' => '123 Freedom Blvd. #123', - '_address_suffix' => '', - '_address_telephone' => '022-333-4455', - '_address_vat_id' => '', - '_address_default_billing_' => '1', - '_address_default_shipping_' => '1', - ]; - $generator = new Generator($pattern, $customersNumber); - /** @var \Magento\ImportExport\Model\Import $import */ - $import = $this->fixtureModel->getObjectManager()->create( - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'customer_composite', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors' - ] - ] + + /** @var CustomerDataGenerator $customerDataGenerator */ + $customerDataGenerator = $this->customerDataGeneratorFactory->create( + $this->getCustomersConfig() ); - // it is not obvious, but the validateSource() will actually save import queue data to DB - $import->validateSource($generator); - // this converts import queue into actual entities - $import->importSource(); + + $fixtureMap = [ + 'customer_data' => function ($customerId) use ($customerDataGenerator) { + return $customerDataGenerator->generate($customerId); + }, + ]; + + $this->customerGenerator->generate($customersNumber, $fixtureMap); + } + + /** + * @return int + */ + private function getCustomersAmount() + { + return max(0, $this->fixtureModel->getValue('customers', 0) - $this->collectionFactory->create()->getSize()); } /** @@ -125,4 +135,12 @@ public function introduceParamLabels() 'customers' => 'Customers' ]; } + + /** + * @return array + */ + private function getCustomersConfig() + { + return $this->fixtureModel->getValue('customer-config', $this->defaultCustomerConfig); + } } diff --git a/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php b/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php index ccbbfefba0968..ed731b8da6712 100644 --- a/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/EavVariationsFixture.php @@ -1,13 +1,21 @@ eavConfig = $eavConfig; + $this->cache = $cache; + $this->storeManager = $storeManager; + $this->attributeSet = $attributeSet; + $this->attributeFactory = $attributeFactory; + } + /** * {@inheritdoc} */ public function execute() { - $configurablesCount = $this->fixtureModel->getValue('configurable_products', 0); - if (!$configurablesCount) { + if (!$this->fixtureModel->getValue('configurable_products', []) + || in_array($this->getAttributeCode(), $this->eavConfig->getEntityAttributeCodes(Product::ENTITY))) { return; } - $this->fixtureModel->resetObjectManager(); - $this->generateAttribute('', $this->fixtureModel->getValue('configurable_products_variation', 3)); - /* @var $cache \Magento\Framework\App\CacheInterface */ - $cache = $this->fixtureModel->getObjectManager()->get(\Magento\Framework\App\CacheInterface::class); + $this->generateAttribute($this->fixtureModel->getValue('configurable_products_variation', 3)); - $cacheKey = \Magento\Eav\Model\Config::ATTRIBUTES_CACHE_ID . \Magento\Catalog\Model\Product::ENTITY; - $cache->remove($cacheKey); + $cacheKey = Config::ATTRIBUTES_CACHE_ID . Product::ENTITY; + $this->cache->remove($cacheKey); } /** @@ -54,18 +109,12 @@ public function introduceParamLabels() } /** - * @param int|string $index * @param int $optionCount * @return void */ - private function generateAttribute($index, $optionCount) + private function generateAttribute($optionCount) { - /* @var $model \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $model = $this->fixtureModel->getObjectManager() - ->create(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - $stores = $storeManager->getStores(); + $stores = $this->storeManager->getStores(); $storeViewsCount = count($stores); $options = []; @@ -81,17 +130,17 @@ private function generateAttribute($index, $optionCount) 'is_required' => '0', 'option' => $options, 'default' => ['option_0'], - 'attribute_code' => 'configurable_variation' . $index, + 'attribute_code' => $this->getAttributeCode(), 'is_global' => '1', 'default_value_text' => '', 'default_value_yesno' => '0', 'default_value_date' => '', 'default_value_textarea' => '', 'is_unique' => '0', - 'is_searchable' => '0', + 'is_searchable' => '1', 'is_visible_in_advanced_search' => '0', 'is_comparable' => '0', - 'is_filterable' => '0', + 'is_filterable' => '1', 'is_filterable_in_search' => '0', 'is_used_for_promo_rules' => '0', 'is_html_allowed_on_front' => '1', @@ -105,15 +154,37 @@ private function generateAttribute($index, $optionCount) 'entity_type_id' => 4, 'is_user_defined' => 1, ]; + + $data['swatch_input_type'] = Swatch::SWATCH_INPUT_TYPE_VISUAL; + $data['swatchvisual']['value'] = array_reduce( + range(1, $optionCount), + function ($values, $index) use ($optionCount) { + $values['option_' . $index] = '#' + . str_repeat( + dechex(255 * $index / $optionCount), + 3 + ); + return $values; + }, + [] + ); + $data['optionvisual']['value'] = array_reduce( + range(1, $optionCount), + function ($values, $index) use ($optionCount) { + $values['option_' . $index] = ['option ' . $index]; + return $values; + }, + [] + ); + /** * The logic is not obvious, but looking to the controller logic for configurable products this attribute * requires to be saved twice to become a child of Default attribute set and become available for creating * and|or importing configurable products. * See MAGETWO-16492 */ - $model->addData($data); - $attributeSet = $this->fixtureModel->getObjectManager()->get(\Magento\Eav\Model\Entity\Attribute\Set::class); - $attributeSet->load(self::ATTRIBUTE_SET_ID); + $model = $this->attributeFactory->create(['data' => $data]); + $attributeSet = $this->attributeSet->load(self::ATTRIBUTE_SET_ID); $model->setAttributeSetId(self::ATTRIBUTE_SET_ID); $model->setAttributeGroupId($attributeSet->getDefaultGroupId(4)); @@ -122,4 +193,12 @@ private function generateAttribute($index, $optionCount) $model->setAttributeSetId(self::ATTRIBUTE_SET_ID); $model->save(); } + + /** + * @return string + */ + private function getAttributeCode() + { + return 'configurable_variation'; + } } diff --git a/setup/src/Magento/Setup/Fixtures/Fixture.php b/setup/src/Magento/Setup/Fixtures/Fixture.php index a4c7aa33b9b89..796c1ee8625c0 100644 --- a/setup/src/Magento/Setup/Fixtures/Fixture.php +++ b/setup/src/Magento/Setup/Fixtures/Fixture.php @@ -1,13 +1,15 @@ introduceParamLabels() as $configName => $label) { + $configValue = $this->fixtureModel->getValue($configName); + $generationCount = is_array($configValue) === true + ? count($configValue[array_keys($configValue)[0]]) + : $configValue; + $output->writeln(' |- ' . $label . ': ' . $generationCount .''); + } + } + /** * Introduce parameters labels * diff --git a/setup/src/Magento/Setup/Fixtures/FixtureConfig.php b/setup/src/Magento/Setup/Fixtures/FixtureConfig.php new file mode 100644 index 0000000000000..e8052253113fd --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/FixtureConfig.php @@ -0,0 +1,75 @@ +parser = $parser; + } + + /** + * Load config from file + * + * @param string $filename + * @throws \Exception + * + * @return void + */ + public function loadConfig($filename) + { + if (!is_readable($filename)) { + throw new \Exception("Profile configuration file `{$filename}` is not readable or does not exists."); + } + $this->parser->getDom()->load($filename); + $this->parser->getDom()->xinclude(); + $this->config = $this->parser->xmlToArray(); + $this->config['config']['profile']['di'] = dirname($filename) . '/' + . (isset($this->config['config']['profile']['di']) + ? $this->config['config']['profile']['di'] + : '../../config/di.xml' + ); + } + + /** + * Get profile configuration value + * + * @param string $key + * @param null|mixed $default + * + * @return mixed + */ + public function getValue($key, $default = null) + { + return isset($this->config['config']['profile'][$key]) ? + ( + // Work around for how attributes are handled in the XML parser when injected via xinclude due to the + // files existing outside of the current working directory. + isset($this->config['config']['profile'][$key]['_value']) ? + $this->config['config']['profile'][$key]['_value'] : $this->config['config']['profile'][$key] + ) : $default; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/FixtureModel.php b/setup/src/Magento/Setup/Fixtures/FixtureModel.php index 118b29cb4306b..e9feed1193314 100644 --- a/setup/src/Magento/Setup/Fixtures/FixtureModel.php +++ b/setup/src/Magento/Setup/Fixtures/FixtureModel.php @@ -1,6 +1,6 @@ initArguments = $initArguments; $this->reindexCommand = $reindexCommand; - $this->fileParser = $fileParser; } /** @@ -114,14 +103,16 @@ public function loadFixtures() $file = basename($file, '.php'); /** @var \Magento\Setup\Fixtures\Fixture $fixture */ $type = 'Magento\Setup\Fixtures' . '\\' . $file; - $fixture = new $type($this); + $fixture = $this->getObjectManager()->create( + $type, + [ + 'fixtureModel' => $this, + ] + ); $this->fixtures[$fixture->getPriority()] = $fixture; } ksort($this->fixtures); - foreach ($this->fixtures as $fixture) { - $this->paramLabels = array_merge($this->paramLabels, $fixture->introduceParamLabels()); - } return $this; } @@ -129,6 +120,7 @@ public function loadFixtures() * Get param labels * * @return array + * @deprecated */ public function getParamLabels() { @@ -160,24 +152,38 @@ public function getObjectManager() $this->objectManager = $objectManagerFactory->create($this->initArguments); $this->objectManager->get(\Magento\Framework\App\State::class)->setAreaCode(self::AREA_CODE); } + return $this->objectManager; } /** - * Init Object Manager + * Init Object Manager * + * @param string $area * @return FixtureModel */ - public function initObjectManager() + public function initObjectManager($area = self::AREA_CODE) { - $this->getObjectManager() - ->configure( - $this->getObjectManager() - ->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class) - ->load(self::AREA_CODE) + $objectManger = $this->getObjectManager(); + $configuration = $objectManger + ->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class) + ->load($area); + $objectManger->configure($configuration); + + $diConfiguration = $this->getValue('di'); + if (file_exists($diConfiguration)) { + $dom = new \DOMDocument(); + $dom->load($diConfiguration); + + $objectManger->configure( + $objectManger + ->get(\Magento\Framework\ObjectManager\Config\Mapper\Dom::class) + ->convert($dom) ); - $this->getObjectManager()->get(\Magento\Framework\Config\ScopeInterface::class) - ->setCurrentScope(self::AREA_CODE); + } + + $objectManger->get(\Magento\Framework\Config\ScopeInterface::class) + ->setCurrentScope($area); return $this; } @@ -185,14 +191,25 @@ public function initObjectManager() * Reset object manager * * @return \Magento\Framework\ObjectManagerInterface + * @deprecated */ public function resetObjectManager() { - $this->objectManager = null; - $this->initObjectManager(); return $this; } + /** + * @return FixtureConfig + */ + private function getConfig() + { + if (null === $this->config) { + $this->config = $this->getObjectManager()->get(FixtureConfig::class); + } + + return $this->config; + } + /** * Load config from file * @@ -203,10 +220,7 @@ public function resetObjectManager() */ public function loadConfig($filename) { - if (!is_readable($filename)) { - throw new \Exception("Profile configuration file `{$filename}` is not readable or does not exists."); - } - $this->config = $this->fileParser->load($filename)->xmlToArray(); + return $this->getConfig()->loadConfig($filename); } /** @@ -219,6 +233,6 @@ public function loadConfig($filename) */ public function getValue($key, $default = null) { - return isset($this->config['config']['profile'][$key]) ? $this->config['config']['profile'][$key] : $default; + return $this->getConfig()->getValue($key, $default); } } diff --git a/setup/src/Magento/Setup/Fixtures/IndexersStatesApplyFixture.php b/setup/src/Magento/Setup/Fixtures/IndexersStatesApplyFixture.php index 9a80889e9c232..f6cc76364f343 100644 --- a/setup/src/Magento/Setup/Fixtures/IndexersStatesApplyFixture.php +++ b/setup/src/Magento/Setup/Fixtures/IndexersStatesApplyFixture.php @@ -1,6 +1,6 @@ storeManager = $storeManager; + $this->productCollectionFactory = $productCollectionFactory; + $this->productRepository = $productRepository; + $this->optionRepository = $optionRepository; + $this->linkManagement = $linkManagement; + $this->serializer = $serializer; + parent::__construct($fixtureModel); + } + + /** + * @inheritdoc + * + * Design of Performance Fixture Generators require generator classes to override Fixture Model's execute method. + * + * @throws \Exception Any exception raised during DB query. + * @return void * @SuppressWarnings(PHPMD) */ public function execute() { - $ordersCount = $this->fixtureModel->getValue('orders', 0); - if ($ordersCount < 1) { - return; - } - $this->fixtureModel->resetObjectManager(); - - $connection = $this->getConnection(); - - $quoteTableName = $this->getTableName( - 'quote', - \Magento\Quote\Model\ResourceModel\Quote::class - ); - $quoteAddressTableName = $this->getTableName( - 'quote_address', - \Magento\Quote\Model\ResourceModel\Quote\Address::class + $orderSimpleCountFrom = (int)$this->fixtureModel->getValue( + 'order_simple_product_count_from', + self::ORDER_SIMPLE_PRODUCT_COUNT_FROM ); - $quoteItemTableName = $this->getTableName( - 'quote_item', - \Magento\Quote\Model\ResourceModel\Quote\Item::class + $orderSimpleCountTo = (int)$this->fixtureModel->getValue( + 'order_simple_product_count_to', + self::ORDER_SIMPLE_PRODUCT_COUNT_TO ); - $quoteItemOptionTableName = $this->getTableName( - 'quote_item_option', - \Magento\Quote\Model\ResourceModel\Quote\Item\Option::class + $orderConfigurableCountFrom = (int)$this->fixtureModel->getValue( + 'order_configurable_product_count_from', + self::ORDER_CONFIGURABLE_PRODUCT_COUNT_FROM ); - $quotePaymentTableName = $this->getTableName( - 'quote_payment', - \Magento\Quote\Model\ResourceModel\Quote\Payment::class + $orderConfigurableCountTo = (int)$this->fixtureModel->getValue( + 'order_configurable_product_count_to', + self::ORDER_CONFIGURABLE_PRODUCT_COUNT_TO ); - $quoteAddressRateTableName = $this->getTableName( - 'quote_shipping_rate', - \Magento\Quote\Model\ResourceModel\Quote\Address\Rate::class + $orderBigConfigurableCountFrom = (int)$this->fixtureModel->getValue( + 'order_big_configurable_product_count_from', + self::ORDER_BIG_CONFIGURABLE_PRODUCT_COUNT_FROM ); - $reportEventTableName = $this->getTableName( - 'report_event', - \Magento\Reports\Model\ResourceModel\Event::class + $orderBigConfigurableCountTo = (int)$this->fixtureModel->getValue( + 'order_big_configurable_product_count_to', + self::ORDER_BIG_CONFIGURABLE_PRODUCT_COUNT_TO ); - $salesOrderTableName = $this->getTableName( + $this->orderQuotesEnable = (bool)$this->fixtureModel->getValue('order_quotes_enable', true); + + $entityId = $this->getMaxEntityId( 'sales_order', - \Magento\Sales\Model\ResourceModel\Order::class + \Magento\Sales\Model\ResourceModel\Order::class, + 'entity_id' ); - $salesOrderAddressTableName = $this->getTableName( - 'sales_order_address', - \Magento\Sales\Model\ResourceModel\Order::class - ); - $salesOrderGridTableName = $this->getTableName( - 'sales_order_grid', - \Magento\Sales\Model\ResourceModel\Order\Grid::class - ); - $salesOrderItemTableName = $this->getTableName( + $requestedOrders = (int)$this->fixtureModel->getValue('orders', 0); + if ($requestedOrders - $entityId < 1) { + return; + } + + $maxItemId = $this->getMaxEntityId( 'sales_order_item', - \Magento\Sales\Model\ResourceModel\Order\Item::class - ); - $salesOrderPaymentTableName = $this->getTableName( - 'sales_order_payment', - \Magento\Sales\Model\ResourceModel\Order\Payment::class + \Magento\Sales\Model\ResourceModel\Order\Item::class, + 'item_id' ); - $salesOrderStatusHistoryTableName = $this->getTableName( - 'sales_order_status_history', - \Magento\Sales\Model\ResourceModel\Order\Status\History::class - ); - $eavEntityStoreTableName = $this->getTableName( - 'eav_entity_store', - \Magento\Eav\Model\ResourceModel\Entity\Store::class - ); - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Category::class); - /** @var $product \Magento\Catalog\Model\Product */ - $product = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Product::class); + $maxItemsPerOrder = $orderSimpleCountTo + ($orderConfigurableCountTo + $orderBigConfigurableCountTo) * 2; - $result = []; - $stores = $storeManager->getStores(); - foreach ($stores as $store) { - $storeId = $store->getStoreId(); - $websiteId = $store->getWebsite()->getId(); - $websiteName = $store->getWebsite()->getName(); - $groupName = $store->getGroup()->getName(); - $storeName = $store->getName(); - $storeRootCategory = $store->getRootCategoryId(); - $category->load($storeRootCategory); - $categoryResource = $category->getResource(); - //Get all categories - $resultsCategories = $categoryResource->getAllChildren($category); - foreach ($resultsCategories as $resultsCategory) { - $category->load($resultsCategory); - $structure = explode('/', $category->getPath()); - $pathSize = count($structure); - if ($pathSize > 1) { - $path = []; - for ($i = 1; $i < $pathSize; $i++) { - $path[] = $category->load($structure[$i])->getName(); - } - array_shift($path); - $resultsCategoryName = implode('/', $path); - } else { - $resultsCategoryName = $category->getName(); - } - //Not use root categories - if (trim($resultsCategoryName) != '') { - /** @var $productCategory \Magento\Catalog\Model\Category */ - $productCategory = $this->fixtureModel->getObjectManager() - ->get(\Magento\Catalog\Model\Category::class); - - /** @var $simpleProductCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */ - $simpleProductCollection = $this->fixtureModel->getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Product\Collection::class - ); - - $simpleProductCollection->addStoreFilter($storeId); - $simpleProductCollection->addWebsiteFilter($websiteId); - $simpleProductCollection->addCategoryFilter($productCategory->load($resultsCategory)); - $simpleProductCollection->getSelect()->where(" type_id = 'simple' "); - $simpleIds = $simpleProductCollection->getAllIds(2); - $simpleProductsResult = []; - foreach ($simpleIds as $key => $simpleId) { - $simpleProduct = $product->load($simpleId); - $simpleProductsResult[$key]['simpleProductId'] = $simpleId; - $simpleProductsResult[$key]['simpleProductSku'] = $simpleProduct->getSku(); - $simpleProductsResult[$key]['simpleProductName'] = $simpleProduct->getName(); - } + /** @var \Generator $itemIdSequence */ + $itemIdSequence = $this->getItemIdSequence($maxItemId, $requestedOrders, $maxItemsPerOrder); - $result[] = [ - $storeId, - $websiteName. '\n'. $groupName . '\n' . $storeName, - $simpleProductsResult - ]; - } + $this->prepareQueryTemplates(); + + $result = []; + foreach ($this->storeManager->getStores() as $store) { + $productsResult = []; + $this->storeManager->setCurrentStore($store->getId()); + + if ($orderSimpleCountTo > 0) { + $productsResult[Type::TYPE_SIMPLE] = $this->prepareSimpleProducts( + $this->getProductIds($store, Type::TYPE_SIMPLE, $orderSimpleCountTo) + ); + } + if ($orderConfigurableCountTo > 0) { + $productsResult[Configurable::TYPE_CODE] = $this->prepareConfigurableProducts( + $this->getProductIds($store, Configurable::TYPE_CODE, $orderConfigurableCountTo) + ); } + if ($orderBigConfigurableCountTo > 0) { + $productsResult[self::BIG_CONFIGURABLE_TYPE] = $this->prepareConfigurableProducts( + $this->getProductIds($store, self::BIG_CONFIGURABLE_TYPE, $orderBigConfigurableCountTo) + ); + } + + $result[] = [ + $store->getId(), + implode(PHP_EOL, [ + $this->storeManager->getWebsite($store->getWebsiteId())->getName(), + $this->storeManager->getGroup($store->getStoreGroupId())->getName(), + $store->getName() + ]), + $productsResult + ]; + } $productStoreId = function ($index) use ($result) { @@ -163,121 +256,427 @@ public function execute() $productStoreName = function ($index) use ($result) { return $result[$index % count($result)][1]; }; - - $simpleProductId[0] = function ($index) use ($result) { - return $result[$index % count($result)][2][0]['simpleProductId']; + $productId = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['id']; }; - $simpleProductId[1] = function ($index) use ($result) { - return $result[$index % count($result)][2][1]['simpleProductId']; + $productSku = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['sku']; }; - $simpleProductSku[0] = function ($index) use ($result) { - return $result[$index % count($result)][2][0]['simpleProductSku']; + $productName = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['name']; }; - $simpleProductSku[1] = function ($index) use ($result) { - return $result[$index % count($result)][2][1]['simpleProductSku']; + $productBuyRequest = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['buyRequest']; }; - $simpleProductName[0] = function ($index) use ($result) { - return $result[$index % count($result)][2][0]['simpleProductName']; + $productChildBuyRequest = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['childBuyRequest']; }; - $simpleProductName[1] = function ($index) use ($result) { - return $result[$index % count($result)][2][1]['simpleProductName']; + $productChildId = function ($entityId, $index, $type) use ($result) { + return $result[$entityId % count($result)][2][$type][$index]['childId']; }; - $entityId = 1; - while ($entityId <= $ordersCount) { - $queries = []; - - $orderNumber = 100000000 * $productStoreId($entityId) + $entityId; - $email = 'order_' . $entityId . '@example.com'; - $firstName = 'First Name'; - $lastName = 'Last Name'; - $company = 'Company'; - $address = 'Address'; - $city = 'City'; - $state = 'Alabama'; - $country = 'US'; - $zip = '11111'; - $phone = '911'; - $dateStart = new \DateTime(); - $time = $dateStart->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); - - $simpleProductIdLen[0] = strlen($simpleProductId[0]($entityId)); - $simpleProductIdLen[1] = strlen($simpleProductId[1]($entityId)); - - //@codingStandardsIgnoreStart - - $queries[] = "INSERT INTO `{$eavEntityStoreTableName}` (`entity_store_id`, `entity_type_id`, `store_id`, `increment_prefix`, `increment_last_id`) VALUES ({$productStoreId($entityId)}, 5, {$productStoreId($entityId)}, '{$productStoreId($entityId)}', '{$orderNumber}') ON DUPLICATE KEY UPDATE `increment_last_id`='{$orderNumber}';"; - - $quoteId = $entityId; - $queries[] = "INSERT INTO `{$quoteTableName}` (`entity_id`, `store_id`, `created_at`, `updated_at`, `converted_at`, `is_active`, `is_virtual`, `is_multi_shipping`, `items_count`, `items_qty`, `orig_order_id`, `store_to_base_rate`, `store_to_quote_rate`, `base_currency_code`, `store_currency_code`, `quote_currency_code`, `grand_total`, `base_grand_total`, `checkout_method`, `customer_id`, `customer_tax_class_id`, `customer_group_id`, `customer_email`, `customer_prefix`, `customer_firstname`, `customer_middlename`, `customer_lastname`, `customer_suffix`, `customer_dob`, `customer_note`, `customer_note_notify`, `customer_is_guest`, `remote_ip`, `applied_rule_ids`, `reserved_order_id`, `password_hash`, `coupon_code`, `global_currency_code`, `base_to_global_rate`, `base_to_quote_rate`, `customer_taxvat`, `customer_gender`, `subtotal`, `base_subtotal`, `subtotal_with_discount`, `base_subtotal_with_discount`, `is_changed`, `trigger_recollect`, `ext_shipping_info`, `is_persistent`, `gift_message_id`) VALUES ({$quoteId}, {$productStoreId($entityId)}, '{$time}', '1970-01-01 03:00:00', NULL, 0, 0, 0, 2, 2.0000, 0, 0.0000, 0.0000, 'USD', 'USD', 'USD', 25.3000, 25.3000, 'guest', NULL, 3, 0, '{$email}', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, 1, '127.0.0.1', '1', NULL, NULL, NULL, 'USD', 1.0000, 1.0000, NULL, NULL, 17.0000, 17.0000, 15.3000, 15.3000, 1, 0, NULL, 0, NULL);"; - - $quoteAddressId[0] = $entityId * 2 - 1; - $quoteAddressId[1] = $entityId * 2; - $queries[] = "INSERT INTO `{$quoteAddressTableName}` (`address_id`, `quote_id`, `created_at`, `updated_at`, `customer_id`, `save_in_address_book`, `customer_address_id`, `address_type`, `email`, `prefix`, `firstname`, `middlename`, `lastname`, `suffix`, `company`, `street`, `city`, `region`, `region_id`, `postcode`, `country_id`, `telephone`, `fax`, `same_as_billing`, `collect_shipping_rates`, `shipping_method`, `shipping_description`, `weight`, `subtotal`, `base_subtotal`, `subtotal_with_discount`, `base_subtotal_with_discount`, `tax_amount`, `base_tax_amount`, `shipping_amount`, `base_shipping_amount`, `shipping_tax_amount`, `base_shipping_tax_amount`, `discount_amount`, `base_discount_amount`, `grand_total`, `base_grand_total`, `customer_notes`, `applied_taxes`, `discount_description`, `shipping_discount_amount`, `base_shipping_discount_amount`, `subtotal_incl_tax`, `base_subtotal_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `shipping_discount_tax_compensation_amount`, `base_shipping_discount_tax_compensation_amnt`, `shipping_incl_tax`, `base_shipping_incl_tax`, `free_shipping`, `vat_id`, `vat_is_valid`, `vat_request_id`, `vat_request_date`, `vat_request_success`, `gift_message_id`) VALUES ({$quoteAddressId[0]}, {$quoteId}, '{$time}', '1970-01-01 03:00:00', NULL, 1, NULL, 'billing', '{$email}', NULL, '{$firstName}', NULL, '{$lastName}', NULL, '{$company}', '{$address}', '{$city}', '{$state}', 1, '{$zip}', '{$country}', '{$phone}', NULL, 0, 0, NULL, NULL, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, NULL, NULL, 0.0000, 0.0000, 0.0000, 0.0000, NULL, NULL, NULL, NULL, NULL, 0.0000, NULL, 0.0000, 0.0000, 0.0000, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);"; - $queries[] = "INSERT INTO `{$quoteAddressTableName}` (`address_id`, `quote_id`, `created_at`, `updated_at`, `customer_id`, `save_in_address_book`, `customer_address_id`, `address_type`, `email`, `prefix`, `firstname`, `middlename`, `lastname`, `suffix`, `company`, `street`, `city`, `region`, `region_id`, `postcode`, `country_id`, `telephone`, `fax`, `same_as_billing`, `collect_shipping_rates`, `shipping_method`, `shipping_description`, `weight`, `subtotal`, `base_subtotal`, `subtotal_with_discount`, `base_subtotal_with_discount`, `tax_amount`, `base_tax_amount`, `shipping_amount`, `base_shipping_amount`, `shipping_tax_amount`, `base_shipping_tax_amount`, `discount_amount`, `base_discount_amount`, `grand_total`, `base_grand_total`, `customer_notes`, `applied_taxes`, `discount_description`, `shipping_discount_amount`, `base_shipping_discount_amount`, `subtotal_incl_tax`, `base_subtotal_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `shipping_discount_tax_compensation_amount`, `base_shipping_discount_tax_compensation_amnt`, `shipping_incl_tax`, `base_shipping_incl_tax`, `free_shipping`, `vat_id`, `vat_is_valid`, `vat_request_id`, `vat_request_date`, `vat_request_success`, `gift_message_id`) VALUES ({$quoteAddressId[1]}, {$quoteId}, '{$time}', '1970-01-01 03:00:00', NULL, 0, NULL, 'shipping', '{$email}', NULL, '{$firstName}', NULL, '{$lastName}', NULL, '{$company}', '{$address}', '{$city}', '{$state}', 1, '{$zip}', '{$country}', '{$phone}', NULL, 1, 0, 'flatrate_flatrate', 'Flat Rate - Fixed', 2.0000, 17.0000, 17.0000, 0.0000, 0.0000, 0.0000, 0.0000, 10.0000, 10.0000, 0.0000, 0.0000, -1.7000, -1.7000, 25.3000, 25.3000, NULL, 'a:0:{}', NULL, 0.0000, 0.0000, 17.0000, NULL, 0.0000, 0.0000, 0.0000, NULL, 10.0000, 10.0000, 0, NULL, NULL, NULL, NULL, NULL, NULL);"; - - $quoteItemId[0] = $entityId * 4 - 3; - $quoteItemId[1] = $entityId * 4 - 2; - $quoteItemId[2] = $entityId * 4 - 1; - $quoteItemId[3] = $entityId * 4; - $queries[] = "INSERT INTO `{$quoteItemTableName}` (`item_id`, `quote_id`, `created_at`, `updated_at`, `product_id`, `store_id`, `parent_item_id`, `is_virtual`, `sku`, `name`, `description`, `applied_rule_ids`, `additional_data`, `is_qty_decimal`, `no_discount`, `weight`, `qty`, `price`, `base_price`, `custom_price`, `discount_percent`, `discount_amount`, `base_discount_amount`, `tax_percent`, `tax_amount`, `base_tax_amount`, `row_total`, `base_row_total`, `row_total_with_discount`, `row_weight`, `product_type`, `base_tax_before_discount`, `tax_before_discount`, `original_custom_price`, `redirect_url`, `base_cost`, `price_incl_tax`, `base_price_incl_tax`, `row_total_incl_tax`, `base_row_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `free_shipping`, `gift_message_id`, `weee_tax_applied`, `weee_tax_applied_amount`, `weee_tax_applied_row_amount`, `weee_tax_disposition`, `weee_tax_row_disposition`, `base_weee_tax_applied_amount`, `base_weee_tax_applied_row_amnt`, `base_weee_tax_disposition`, `base_weee_tax_row_disposition`) VALUES ({$quoteItemId[0]}, {$quoteId}, '1970-01-01 03:00:00', '1970-01-01 03:00:00', {$simpleProductId[0]($entityId)}, {$productStoreId($entityId)}, NULL, 0, '{$simpleProductSku[0]($entityId)}', '{$simpleProductName[0]($entityId)}', NULL, '1', NULL, 0, 0, 1.0000, 1.0000, 8.5000, 8.5000, NULL, 10.0000, 0.8500, 0.8500, 0.0000, 0.0000, 0.0000, 8.5000, 8.5000, 0.0000, 1.0000, 'simple', NULL, NULL, NULL, NULL, NULL, 8.5000, 8.5000, 8.5000, 8.5000, 0.0000, 0.0000, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);"; - $queries[] = "INSERT INTO `{$quoteItemTableName}` (`item_id`, `quote_id`, `created_at`, `updated_at`, `product_id`, `store_id`, `parent_item_id`, `is_virtual`, `sku`, `name`, `description`, `applied_rule_ids`, `additional_data`, `is_qty_decimal`, `no_discount`, `weight`, `qty`, `price`, `base_price`, `custom_price`, `discount_percent`, `discount_amount`, `base_discount_amount`, `tax_percent`, `tax_amount`, `base_tax_amount`, `row_total`, `base_row_total`, `row_total_with_discount`, `row_weight`, `product_type`, `base_tax_before_discount`, `tax_before_discount`, `original_custom_price`, `redirect_url`, `base_cost`, `price_incl_tax`, `base_price_incl_tax`, `row_total_incl_tax`, `base_row_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `free_shipping`, `gift_message_id`, `weee_tax_applied`, `weee_tax_applied_amount`, `weee_tax_applied_row_amount`, `weee_tax_disposition`, `weee_tax_row_disposition`, `base_weee_tax_applied_amount`, `base_weee_tax_applied_row_amnt`, `base_weee_tax_disposition`, `base_weee_tax_row_disposition`) VALUES ({$quoteItemId[1]}, {$quoteId}, '1970-01-01 03:00:00', '1970-01-01 03:00:00', {$simpleProductId[1]($entityId)}, {$productStoreId($entityId)}, NULL, 0, '{$simpleProductSku[1]($entityId)}', '{$simpleProductName[1]($entityId)}', NULL, '1', NULL, 0, 0, 1.0000, 1.0000, 8.5000, 8.5000, NULL, 10.0000, 0.8500, 0.8500, 0.0000, 0.0000, 0.0000, 8.5000, 8.5000, 0.0000, 1.0000, 'simple', NULL, NULL, NULL, NULL, NULL, 8.5000, 8.5000, 8.5000, 8.5000, 0.0000, 0.0000, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);"; - - $quoteItemOptionId[0] = $entityId * 8 - 7; - $quoteItemOptionId[1] = $entityId * 8 - 6; - $quoteItemOptionId[2] = $entityId * 8 - 5; - $quoteItemOptionId[3] = $entityId * 8 - 4; - $quoteItemOptionId[4] = $entityId * 8 - 3; - $quoteItemOptionId[5] = $entityId * 8 - 2; - $quoteItemOptionId[6] = $entityId * 8 - 1; - $quoteItemOptionId[7] = $entityId * 8; - $queries[] = "INSERT INTO `{$quoteItemOptionTableName}` (`option_id`, `item_id`, `product_id`, `code`, `value`) VALUES ({$quoteItemOptionId[0]}, {$quoteItemId[0]}, {$simpleProductId[0]($entityId)}, 'info_buyRequest', 'a:3:{s:4:\"uenc\";s:44:\"aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw,\";s:7:\"product\";s:{$simpleProductIdLen[0]}:\"{$simpleProductId[0]($entityId)}\";s:3:\"qty\";i:1;}');"; - $queries[] = "INSERT INTO `{$quoteItemOptionTableName}` (`option_id`, `item_id`, `product_id`, `code`, `value`) VALUES ({$quoteItemOptionId[1]}, {$quoteItemId[1]}, {$simpleProductId[1]($entityId)}, 'info_buyRequest', 'a:3:{s:4:\"uenc\";s:44:\"aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw,\";s:7:\"product\";s:{$simpleProductIdLen[1]}:\"{$simpleProductId[1]($entityId)}\";s:3:\"qty\";i:1;}');"; - - $quotePaymentId = $quoteId; - $queries[] = "INSERT INTO `{$quotePaymentTableName}` (`payment_id`, `quote_id`, `created_at`, `updated_at`, `method`, `cc_type`, `cc_number_enc`, `cc_last_4`, `cc_cid_enc`, `cc_owner`, `cc_exp_month`, `cc_exp_year`, `cc_ss_owner`, `cc_ss_start_month`, `cc_ss_start_year`, `po_number`, `additional_data`, `cc_ss_issue`, `additional_information`) VALUES ({$quotePaymentId}, {$quoteId}, '{$time}', '1970-01-01 03:00:00', 'checkmo', NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, NULL, NULL, NULL, NULL);"; - - $quoteShippingRateId = $quoteAddressId[1]; - $queries[] = "INSERT INTO `{$quoteAddressRateTableName}` (`rate_id`, `address_id`, `created_at`, `updated_at`, `carrier`, `carrier_title`, `code`, `method`, `method_description`, `price`, `error_message`, `method_title`) VALUES ({$quoteShippingRateId}, {$quoteAddressId[1]}, '{$time}', '1970-01-01 03:00:00', 'flatrate', 'Flat Rate', 'flatrate_flatrate', 'flatrate', NULL, 10.0000, NULL, 'Fixed');"; - - $reportEventId[0] = $quoteItemId[0]; - $reportEventId[1] = $quoteItemId[1]; - $reportEventId[2] = $quoteItemId[2]; - $reportEventId[3] = $quoteItemId[3]; - $queries[] = "INSERT INTO `{$reportEventTableName}` (`event_id`, `logged_at`, `event_type_id`, `object_id`, `subject_id`, `subtype`, `store_id`) VALUES ({$reportEventId[0]}, '{$time}', 4, {$simpleProductId[0]($entityId)}, 2, 1, {$productStoreId($entityId)});"; - $queries[] = "INSERT INTO `{$reportEventTableName}` (`event_id`, `logged_at`, `event_type_id`, `object_id`, `subject_id`, `subtype`, `store_id`) VALUES ({$reportEventId[1]}, '{$time}', 4, {$simpleProductId[1]($entityId)}, 2, 1, {$productStoreId($entityId)});"; - - $salesOrderId = $quoteId; - $queries[] = "INSERT INTO `{$salesOrderTableName}` (`entity_id`, `state`, `status`, `coupon_code`, `protect_code`, `shipping_description`, `is_virtual`, `store_id`, `customer_id`, `base_discount_amount`, `base_discount_canceled`, `base_discount_invoiced`, `base_discount_refunded`, `base_grand_total`, `base_shipping_amount`, `base_shipping_canceled`, `base_shipping_invoiced`, `base_shipping_refunded`, `base_shipping_tax_amount`, `base_shipping_tax_refunded`, `base_subtotal`, `base_subtotal_canceled`, `base_subtotal_invoiced`, `base_subtotal_refunded`, `base_tax_amount`, `base_tax_canceled`, `base_tax_invoiced`, `base_tax_refunded`, `base_to_global_rate`, `base_to_order_rate`, `base_total_canceled`, `base_total_invoiced`, `base_total_invoiced_cost`, `base_total_offline_refunded`, `base_total_online_refunded`, `base_total_paid`, `base_total_qty_ordered`, `base_total_refunded`, `discount_amount`, `discount_canceled`, `discount_invoiced`, `discount_refunded`, `grand_total`, `shipping_amount`, `shipping_canceled`, `shipping_invoiced`, `shipping_refunded`, `shipping_tax_amount`, `shipping_tax_refunded`, `store_to_base_rate`, `store_to_order_rate`, `subtotal`, `subtotal_canceled`, `subtotal_invoiced`, `subtotal_refunded`, `tax_amount`, `tax_canceled`, `tax_invoiced`, `tax_refunded`, `total_canceled`, `total_invoiced`, `total_offline_refunded`, `total_online_refunded`, `total_paid`, `total_qty_ordered`, `total_refunded`, `can_ship_partially`, `can_ship_partially_item`, `customer_is_guest`, `customer_note_notify`, `billing_address_id`, `customer_group_id`, `edit_increment`, `email_sent`, `send_email`, `forced_shipment_with_invoice`, `payment_auth_expiration`, `quote_address_id`, `quote_id`, `shipping_address_id`, `adjustment_negative`, `adjustment_positive`, `base_adjustment_negative`, `base_adjustment_positive`, `base_shipping_discount_amount`, `base_subtotal_incl_tax`, `base_total_due`, `payment_authorization_amount`, `shipping_discount_amount`, `subtotal_incl_tax`, `total_due`, `weight`, `customer_dob`, `increment_id`, `applied_rule_ids`, `base_currency_code`, `customer_email`, `customer_firstname`, `customer_lastname`, `customer_middlename`, `customer_prefix`, `customer_suffix`, `customer_taxvat`, `discount_description`, `ext_customer_id`, `ext_order_id`, `global_currency_code`, `hold_before_state`, `hold_before_status`, `order_currency_code`, `original_increment_id`, `relation_child_id`, `relation_child_real_id`, `relation_parent_id`, `relation_parent_real_id`, `remote_ip`, `shipping_method`, `store_currency_code`, `store_name`, `x_forwarded_for`, `customer_note`, `created_at`, `updated_at`, `total_item_count`, `customer_gender`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `shipping_discount_tax_compensation_amount`, `base_shipping_discount_tax_compensation_amnt`, `discount_tax_compensation_invoiced`, `base_discount_tax_compensation_invoiced`, `discount_tax_compensation_refunded`, `base_discount_tax_compensation_refunded`, `shipping_incl_tax`, `base_shipping_incl_tax`, `coupon_rule_name`, `gift_message_id`) VALUES ({$salesOrderId}, 'new', 'pending', NULL, '272ecb', 'Flat Rate - Fixed', 0, {$productStoreId($entityId)}, NULL, -1.7000, NULL, NULL, NULL, 25.3000, 10.0000, NULL, NULL, NULL, 0.0000, NULL, 17.0000, NULL, NULL, NULL, 0.0000, NULL, NULL, NULL, 1.0000, 1.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -1.7000, NULL, NULL, NULL, 25.3000, 10.0000, NULL, NULL, NULL, 0.0000, NULL, 0.0000, 0.0000, 17.0000, NULL, NULL, NULL, 0.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 2.0000, NULL, NULL, NULL, 1, 1, 2, 0, NULL, 1, 1, NULL, NULL, NULL, 1, 1, NULL, NULL, NULL, NULL, NULL, 17.0000, 25.3000, NULL, NULL, 17.0000, 25.3000, 2.0000, NULL, {$orderNumber}, '1', 'USD', '{$email}', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'USD', NULL, NULL, 'USD', NULL, NULL, NULL, NULL, NULL, '127.0.0.1', 'flatrate_flatrate', 'USD', '{$productStoreName($entityId)}', NULL, NULL, '{$time}', '{$time}', 2, NULL, 0.0000, 0.0000, 0.0000, NULL, NULL, NULL, NULL, NULL, 10.0000, 10.0000, NULL, NULL);"; - - $salesOrderAddressId[0] = $quoteAddressId[0]; - $salesOrderAddressId[1] = $quoteAddressId[1]; - $queries[] = "INSERT INTO `{$salesOrderAddressTableName}` (`entity_id`, `parent_id`, `customer_address_id`, `quote_address_id`, `region_id`, `customer_id`, `fax`, `region`, `postcode`, `lastname`, `street`, `city`, `email`, `telephone`, `country_id`, `firstname`, `address_type`, `prefix`, `middlename`, `suffix`, `company`, `vat_id`, `vat_is_valid`, `vat_request_id`, `vat_request_date`, `vat_request_success`) VALUES ({$salesOrderAddressId[0]}, {$salesOrderId}, NULL, NULL, 1, NULL, NULL, '{$state}', '{$zip}', '{$lastName}', '{$address}', '{$city}', '{$email}', '{$phone}', '{$country}', '{$firstName}', 'shipping', NULL, NULL, NULL, '{$company}', NULL, NULL, NULL, NULL, NULL);"; - $queries[] = "INSERT INTO `{$salesOrderAddressTableName}` (`entity_id`, `parent_id`, `customer_address_id`, `quote_address_id`, `region_id`, `customer_id`, `fax`, `region`, `postcode`, `lastname`, `street`, `city`, `email`, `telephone`, `country_id`, `firstname`, `address_type`, `prefix`, `middlename`, `suffix`, `company`, `vat_id`, `vat_is_valid`, `vat_request_id`, `vat_request_date`, `vat_request_success`) VALUES ({$salesOrderAddressId[1]}, {$salesOrderId}, NULL, NULL, 1, NULL, NULL, '{$state}', '{$zip}', '{$lastName}', '{$address}', '{$city}', '{$email}', '{$phone}', '{$country}', '{$firstName}', 'billing', NULL, NULL, NULL, '{$company}', NULL, NULL, NULL, NULL, NULL);"; - - $salesOrderGridId = $salesOrderId; - $queries[] = "INSERT INTO `{$salesOrderGridTableName}` (`entity_id`, `status`, `store_id`, `store_name`, `customer_id`, `base_grand_total`, `base_total_paid`, `grand_total`, `total_paid`, `increment_id`, `base_currency_code`, `order_currency_code`, `shipping_name`, `billing_name`, `created_at`, `updated_at`) VALUES ({$salesOrderGridId}, 'pending', {$productStoreId($entityId)}, '{$productStoreName($entityId)}', NULL, 25.3000, NULL, 25.3000, NULL, {$orderNumber}, 'USD', 'USD', '', '', '{$time}', NULL);"; - - $salesOrderItemId[0] = $quoteItemId[0]; - $salesOrderItemId[1] = $quoteItemId[1]; - $salesOrderItemId[2] = $quoteItemId[2]; - $salesOrderItemId[3] = $quoteItemId[3]; - $queries[] = "INSERT INTO `{$salesOrderItemTableName}` (`item_id`, `order_id`, `parent_item_id`, `quote_item_id`, `store_id`, `created_at`, `updated_at`, `product_id`, `product_type`, `product_options`, `weight`, `is_virtual`, `sku`, `name`, `description`, `applied_rule_ids`, `additional_data`, `is_qty_decimal`, `no_discount`, `qty_backordered`, `qty_canceled`, `qty_invoiced`, `qty_ordered`, `qty_refunded`, `qty_shipped`, `base_cost`, `price`, `base_price`, `original_price`, `base_original_price`, `tax_percent`, `tax_amount`, `base_tax_amount`, `tax_invoiced`, `base_tax_invoiced`, `discount_percent`, `discount_amount`, `base_discount_amount`, `discount_invoiced`, `base_discount_invoiced`, `amount_refunded`, `base_amount_refunded`, `row_total`, `base_row_total`, `row_invoiced`, `base_row_invoiced`, `row_weight`, `base_tax_before_discount`, `tax_before_discount`, `ext_order_item_id`, `locked_do_invoice`, `locked_do_ship`, `price_incl_tax`, `base_price_incl_tax`, `row_total_incl_tax`, `base_row_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `discount_tax_compensation_invoiced`, `base_discount_tax_compensation_invoiced`, `discount_tax_compensation_refunded`, `base_discount_tax_compensation_refunded`, `tax_canceled`, `discount_tax_compensation_canceled`, `tax_refunded`, `base_tax_refunded`, `discount_refunded`, `base_discount_refunded`, `free_shipping`, `gift_message_id`, `gift_message_available`, `weee_tax_applied`, `weee_tax_applied_amount`, `weee_tax_applied_row_amount`, `weee_tax_disposition`, `weee_tax_row_disposition`, `base_weee_tax_applied_amount`, `base_weee_tax_applied_row_amnt`, `base_weee_tax_disposition`, `base_weee_tax_row_disposition`) VALUES ({$salesOrderItemId[0]}, {$salesOrderId}, NULL, {$quoteItemId[0]}, {$productStoreId($entityId)}, '{$time}', '0000-00-00 00:00:00', {$simpleProductId[0]($entityId)}, 'simple', 'a:1:{s:15:\"info_buyRequest\";a:3:{s:4:\"uenc\";s:44:\"aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw,\";s:7:\"product\";s:{$simpleProductIdLen[0]}:\"{$simpleProductId[0]($entityId)}\";s:3:\"qty\";i:1;}}', 1.0000, 0, '{$simpleProductSku[0]($entityId)}', '{$simpleProductName[0]($entityId)}', NULL, '1', NULL, 0, 0, NULL, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, NULL, 8.5000, 8.5000, 10.0000, 10.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 10.0000, 0.8500, 0.8500, 0.0000, 0.0000, 0.0000, 0.0000, 8.5000, 8.5000, 0.0000, 0.0000, 1.0000, NULL, NULL, NULL, NULL, NULL, 8.5000, 8.5000, 8.5000, 8.5000, 0.0000, 0.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);"; - $queries[] = "INSERT INTO `{$salesOrderItemTableName}` (`item_id`, `order_id`, `parent_item_id`, `quote_item_id`, `store_id`, `created_at`, `updated_at`, `product_id`, `product_type`, `product_options`, `weight`, `is_virtual`, `sku`, `name`, `description`, `applied_rule_ids`, `additional_data`, `is_qty_decimal`, `no_discount`, `qty_backordered`, `qty_canceled`, `qty_invoiced`, `qty_ordered`, `qty_refunded`, `qty_shipped`, `base_cost`, `price`, `base_price`, `original_price`, `base_original_price`, `tax_percent`, `tax_amount`, `base_tax_amount`, `tax_invoiced`, `base_tax_invoiced`, `discount_percent`, `discount_amount`, `base_discount_amount`, `discount_invoiced`, `base_discount_invoiced`, `amount_refunded`, `base_amount_refunded`, `row_total`, `base_row_total`, `row_invoiced`, `base_row_invoiced`, `row_weight`, `base_tax_before_discount`, `tax_before_discount`, `ext_order_item_id`, `locked_do_invoice`, `locked_do_ship`, `price_incl_tax`, `base_price_incl_tax`, `row_total_incl_tax`, `base_row_total_incl_tax`, `discount_tax_compensation_amount`, `base_discount_tax_compensation_amount`, `discount_tax_compensation_invoiced`, `base_discount_tax_compensation_invoiced`, `discount_tax_compensation_refunded`, `base_discount_tax_compensation_refunded`, `tax_canceled`, `discount_tax_compensation_canceled`, `tax_refunded`, `base_tax_refunded`, `discount_refunded`, `base_discount_refunded`, `free_shipping`, `gift_message_id`, `gift_message_available`, `weee_tax_applied`, `weee_tax_applied_amount`, `weee_tax_applied_row_amount`, `weee_tax_disposition`, `weee_tax_row_disposition`, `base_weee_tax_applied_amount`, `base_weee_tax_applied_row_amnt`, `base_weee_tax_disposition`, `base_weee_tax_row_disposition`) VALUES ({$salesOrderItemId[1]}, {$salesOrderId}, NULL, {$quoteItemId[1]}, {$productStoreId($entityId)}, '{$time}', '0000-00-00 00:00:00', {$simpleProductId[1]($entityId)}, 'simple', 'a:1:{s:15:\"info_buyRequest\";a:3:{s:4:\"uenc\";s:44:\"aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw,\";s:7:\"product\";s:{$simpleProductIdLen[1]}:\"{$simpleProductId[1]($entityId)}\";s:3:\"qty\";i:1;}}', 1.0000, 0, '{$simpleProductSku[1]($entityId)}', '{$simpleProductName[1]($entityId)}', NULL, '1', NULL, 0, 0, NULL, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, NULL, 8.5000, 8.5000, 10.0000, 10.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 10.0000, 0.8500, 0.8500, 0.0000, 0.0000, 0.0000, 0.0000, 8.5000, 8.5000, 0.0000, 0.0000, 1.0000, NULL, NULL, NULL, NULL, NULL, 8.5000, 8.5000, 8.5000, 8.5000, 0.0000, 0.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);"; - - $salesOrderPaymentId = $salesOrderId; - $queries[] = "INSERT INTO `{$salesOrderPaymentTableName}` (`entity_id`, `parent_id`, `base_shipping_captured`, `shipping_captured`, `amount_refunded`, `base_amount_paid`, `amount_canceled`, `base_amount_authorized`, `base_amount_paid_online`, `base_amount_refunded_online`, `base_shipping_amount`, `shipping_amount`, `amount_paid`, `amount_authorized`, `base_amount_ordered`, `base_shipping_refunded`, `shipping_refunded`, `base_amount_refunded`, `amount_ordered`, `base_amount_canceled`, `quote_payment_id`, `additional_data`, `cc_exp_month`, `cc_ss_start_year`, `echeck_bank_name`, `method`, `cc_debug_request_body`, `cc_secure_verify`, `protection_eligibility`, `cc_approval`, `cc_last_4`, `cc_status_description`, `echeck_type`, `cc_debug_response_serialized`, `cc_ss_start_month`, `echeck_account_type`, `last_trans_id`, `cc_cid_status`, `cc_owner`, `cc_type`, `po_number`, `cc_exp_year`, `cc_status`, `echeck_routing_number`, `account_status`, `anet_trans_method`, `cc_debug_response_body`, `cc_ss_issue`, `echeck_account_name`, `cc_avs_status`, `cc_number_enc`, `cc_trans_id`, `address_status`, `additional_information`) VALUES ({$salesOrderPaymentId}, {$salesOrderId}, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 10.0000, 10.0000, NULL, NULL, 25.3000, NULL, NULL, NULL, 25.3000, NULL, NULL, NULL, NULL, '0', NULL, 'checkmo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'a:1:{s:53:\"a:1:{s:12:\"method_title\";s:19:\"Check / Money order\";}\";N;}');"; - - $salesOrderStatusHistoryId = $salesOrderId; - $queries[] = "INSERT INTO `{$salesOrderStatusHistoryTableName}` (`entity_id`, `parent_id`, `is_customer_notified`, `is_visible_on_front`, `comment`, `status`, `created_at`, `entity_name`) VALUES ({$salesOrderStatusHistoryId}, {$salesOrderId}, 1, 0, NULL, 'pending', '{$time}', 'order');"; - - // @codingStandardsIgnoreEnd - foreach ($queries as $query) { - $connection->query($query); + $address = [ + '%firstName%' => 'First Name', + '%lastName%' => 'Last Name', + '%company%' => 'Company', + '%address%' => 'Address', + '%city%' => 'city', + '%state%' => 'Alabama', + '%country%' => 'US', + '%zip%' => '11111', + '%phone%' => '911' + ]; + + $batchNumber = 0; + $entityId++; + while ($entityId <= $requestedOrders) { + $batchNumber++; + $productCount = [ + Type::TYPE_SIMPLE => mt_rand($orderSimpleCountFrom, $orderSimpleCountTo), + Configurable::TYPE_CODE => mt_rand($orderConfigurableCountFrom, $orderConfigurableCountTo), + self::BIG_CONFIGURABLE_TYPE => mt_rand($orderBigConfigurableCountFrom, $orderBigConfigurableCountTo) + ]; + $order = [ + '%itemsPerOrder%' => array_sum($productCount), + '%orderNumber%' => 100000000 * $productStoreId($entityId) + $entityId, + '%email%' => "order_{$entityId}@example.com", + '%time%' => date(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), + '%productStoreId%' => $productStoreId($entityId), + '%productStoreName%' => $productStoreName($entityId), + '%entityId%' => $entityId, + ]; + $shippingAddress = ['%orderAddressId%' => $entityId * 2 - 1, '%addressType%' => 'shipping']; + $billingAddress = ['%orderAddressId%' => $entityId * 2, '%addressType%' => 'billing']; + + try { + $this->query('quote', $order); + $this->query('quote_address', $order, $address, $shippingAddress); + $this->query('quote_address', $order, $address, $billingAddress); + $this->query('quote_payment', $order); + $this->query('quote_shipping_rate', $order, $shippingAddress); + + $this->query('eav_entity_store', $order); + $this->query('sales_order', $order); + $this->query('sales_order_address', $order, $address, $shippingAddress); + $this->query('sales_order_address', $order, $address, $billingAddress); + $this->query('sales_order_grid', $order); + $this->query('sales_order_payment', $order); + $this->query('sales_order_status_history', $order); + + for ($i = 0; $i < $productCount[Type::TYPE_SIMPLE]; $i++) { + $itemData = [ + '%productId%' => $productId($entityId, $i, Type::TYPE_SIMPLE), + '%sku%' => $productSku($entityId, $i, Type::TYPE_SIMPLE), + '%name%' => $productName($entityId, $i, Type::TYPE_SIMPLE), + '%itemId%' => $itemIdSequence->current(), + '%productType%' => Type::TYPE_SIMPLE, + '%productOptions%' => $productBuyRequest($entityId, $i, Type::TYPE_SIMPLE), + '%parentItemId%' => 'null', + ]; + $this->query('sales_order_item', $order, $itemData); + $this->query('quote_item', $order, $itemData); + $this->query('quote_item_option', $order, $itemData, [ + '%code%' => 'info_buyRequest', + '%value%' => $this->serializer->serialize([ + 'product' => $productId($entityId, $i, Type::TYPE_SIMPLE), + 'qty' => "1", + 'uenc' => 'aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw' + ]) + ]); + $itemIdSequence->next(); + } + + foreach ([Configurable::TYPE_CODE, self::BIG_CONFIGURABLE_TYPE] as $type) { + for ($i = 0; $i < $productCount[$type]; $i++) { + // Generate parent item + $parentItemId = $itemIdSequence->current(); + $itemData = [ + '%productId%' => $productId($entityId, $i, $type), + '%sku%' => $productSku($entityId, $i, $type), + '%name%' => $productName($entityId, $i, $type), + '%productOptions%' => $productBuyRequest($entityId, $i, $type)['order'], + '%itemId%' => $parentItemId, + '%parentItemId%' => 'null', + '%productType%' => Configurable::TYPE_CODE + ]; + $this->query('sales_order_item', $order, $itemData); + $this->query('quote_item', $order, $itemData); + $this->query('quote_item_option', $order, $itemData, [ + '%code%' => 'info_buyRequest', + '%value%' => $productBuyRequest($entityId, $i, $type)['quote'] + ]); + $this->query('quote_item_option', $order, $itemData, [ + '%code%' => 'attributes', + '%value%' => $productBuyRequest($entityId, $i, $type)['super_attribute'] + ]); + $itemData['%productId%'] = $productChildId($entityId, $i, $type); + $this->query('quote_item_option', $itemData, [ + '%code%' => "product_qty_" . $productChildId($entityId, $i, $type), + '%value%' => "1" + ]); + $this->query('quote_item_option', $itemData, [ + '%code%' => "simple_product", + '%value%' => $productChildId($entityId, $i, $type) + ]); + $itemIdSequence->next(); + + // Generate child item + $itemData = [ + '%productId%' => $productChildId($entityId, $i, $type), + '%sku%' => $productSku($entityId, $i, $type), + '%name%' => $productName($entityId, $i, $type), + '%productOptions%' => $productChildBuyRequest($entityId, $i, $type)['order'], + '%itemId%' => $itemIdSequence->current(), + '%parentItemId%' => $parentItemId, + '%productType%' => Type::TYPE_SIMPLE + ]; + + $this->query('sales_order_item', $order, $itemData); + $this->query('quote_item', $order, $itemData); + $this->query('quote_item_option', $itemData, [ + '%code%' => "info_buyRequest", + '%value%' => $productChildBuyRequest($entityId, $i, $type)['quote'] + ]); + $this->query('quote_item_option', $itemData, [ + '%code%' => "parent_product_id", + '%value%' => $productId($entityId, $i, $type) + ]); + $itemIdSequence->next(); + } + } + } catch (\Exception $lastException) { + foreach ($this->resourceConnections as $connection) { + if ($connection->getTransactionLevel() > 0) { + $connection->rollBack(); + } + } + throw $lastException; } + if ($batchNumber >= self::BATCH_SIZE) { + $this->commitBatch(); + $batchNumber = 0; + } $entityId++; } + + foreach ($this->resourceConnections as $connection) { + if ($connection->getTransactionLevel() > 0) { + $connection->commit(); + } + } + } + + /** + * Load and prepare INSERT query templates data from external file. + * + * Queries are prepared using external json file, where keys are DB column names and values represent data, + * to be inserted to the table. Data may contain a default value or a placeholder which is replaced later during + * flow (in the query method of this class). + * Additionally, in case if multiple DB connections are set up, transaction is started for each connection. + * + * @return void + */ + private function prepareQueryTemplates() + { + $fileName = __DIR__ . DIRECTORY_SEPARATOR . "_files" . DIRECTORY_SEPARATOR . "orders_fixture_data.json"; + $templateData = json_decode(file_get_contents(realpath($fileName)), true); + foreach ($templateData as $table => $template) { + if (isset($template['_table'])) { + $table = $template['_table']; + unset($template['_table']); + } + if (isset($template['_resource'])) { + $resource = $template['_resource']; + unset($template['_resource']); + } else { + $resource = explode("_", $table); + foreach ($resource as &$item) { + $item = ucfirst($item); + } + $resource = "Magento\\" + . array_shift($resource) + . "\\Model\\ResourceModel\\" + . implode("\\", $resource); + } + + $tableName = $this->getTableName($table, $resource); + + $querySuffix = ""; + if (isset($template['_query_suffix'])) { + $querySuffix = $template['_query_suffix']; + unset($template['_query_suffix']); + } + + $fields = implode(', ', array_keys($template)); + $values = implode(', ', array_values($template)); + + /** @var \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resourceModel */ + $resourceModel = $this->fixtureModel->getObjectManager()->get($resource); + $connection = $resourceModel->getConnection(); + if ($connection->getTransactionLevel() == 0) { + $connection->beginTransaction(); + } + + $this->queryTemplates[$table] = "INSERT INTO `{$tableName}` ({$fields}) VALUES ({$values}){$querySuffix};"; + $this->resourceConnections[$table] = $connection; + } + } + + /** + * Build and execute query. + * + * Builds a database query by replacing placeholder values in the cached queries and executes query in appropriate + * DB connection (if setup). Additionally filters out quote-related queries, if appropriate flag is set. + * + * @param string $table + * @param array ...$replacements + * @return void + */ + protected function query($table, ... $replacements) + { + if (!$this->orderQuotesEnable && strpos($table, "quote") !== false) { + return; + } + $query = $this->queryTemplates[$table]; + foreach ($replacements as $data) { + $query = str_replace(array_keys($data), array_values($data), $query); + } + + $this->resourceConnections[$table]->query($query); + } + + /** + * Get maximum order id currently existing in the database. + * + * To support incremental generation of the orders it is necessary to get the maximum order entity_id currently + * existing in the database. + * + * @param string $tableName + * @param string $resourceName + * @param string $column + * @return int + */ + private function getMaxEntityId($tableName, $resourceName, $column = 'entity_id') + { + $tableName = $this->getTableName( + $tableName, + $resourceName + ); + + /** @var \Magento\Framework\Model\ResourceModel\Db\VersionControl\AbstractDb $resource */ + $resource = $this->fixtureModel->getObjectManager()->get($resourceName); + $connection = $resource->getConnection(); + return (int)$connection->query("SELECT MAX(`{$column}`) FROM `{$tableName}`;")->fetchColumn(0); + } + + /** + * Get a limited amount of product id's from a collection filtered by store and specific product type. + * + * @param \Magento\Store\Api\Data\StoreInterface $store + * @param string $typeId + * @param int $limit + * @return array + */ + private function getProductIds(\Magento\Store\Api\Data\StoreInterface $store, $typeId, $limit = null) + { + /** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */ + $productCollection = $this->productCollectionFactory->create(); + + $productCollection->addStoreFilter($store->getId()); + $productCollection->addWebsiteFilter($store->getWebsiteId()); + + // "Big%" should be replaced with a configurable value. + if ($typeId === self::BIG_CONFIGURABLE_TYPE) { + $productCollection->getSelect()->where(" type_id = '" . Configurable::TYPE_CODE . "' "); + $productCollection->getSelect()->where(" sku LIKE 'Big%' "); + } else { + $productCollection->getSelect()->where(" type_id = '$typeId' "); + $productCollection->getSelect()->where(" sku NOT LIKE 'Big%' "); + } + + return $productCollection->getAllIds($limit); + } + + /** + * Prepare data for the simple products to be used as order items. + * + * Based on the Product Id's load data, which is required to replace placeholders in queries. + * + * @param array $productIds + * @return array + */ + private function prepareSimpleProducts(array $productIds = []) + { + $productsResult = []; + foreach ($productIds as $key => $simpleId) { + $simpleProduct = $this->productRepository->getById($simpleId); + $productsResult[$key]['id'] = $simpleId; + $productsResult[$key]['sku'] = $simpleProduct->getSku(); + $productsResult[$key]['name'] = $simpleProduct->getName(); + $productsResult[$key]['buyRequest'] = $this->serializer->serialize([ + "info_buyRequest" => [ + "uenc" => "aHR0cDovL21hZ2VudG8uZGV2L2NvbmZpZ3VyYWJsZS1wcm9kdWN0LTEuaHRtbA,,", + "product" => $simpleId, + "qty" => "1" + ] + ]); + } + return $productsResult; + } + + /** + * Prepare data for the configurable products to be used as order items. + * + * Based on the Product Id's load data, which is required to replace placeholders in queries. + * + * @param array $productIds + * @return array + */ + private function prepareConfigurableProducts(array $productIds = []) + { + $productsResult = []; + foreach ($productIds as $key => $configurableId) { + $configurableProduct = $this->productRepository->getById($configurableId); + $options = $this->optionRepository->getList($configurableProduct->getSku()); + $configurableChild = $this->linkManagement->getChildren($configurableProduct->getSku())[0]; + $simpleSku = $configurableChild->getSku(); + $simpleId = $this->productRepository->get($simpleSku)->getId(); + + $attributesInfo = []; + $superAttribute = []; + foreach ($options as $option) { + $attributesInfo[] = [ + "label" => $option->getLabel(), + "value" => $option['options']['0']['label'], + "option_id" => $option->getAttributeId(), + "option_value" => $option->getValues()[0]->getValueIndex() + ]; + $superAttribute[$option->getAttributeId()] = $option->getValues()[0]->getValueIndex(); + } + + $configurableBuyRequest = [ + "info_buyRequest" => [ + "uenc" => "aHR0cDovL21hZ2UyLmNvbS9jYXRlZ29yeS0xLmh0bWw", + "product" => $configurableId, + "selected_configurable_option" => $simpleId, + "related_product" => "", + "super_attribute" => $superAttribute, + "qty" => 1 + ], + "attributes_info" => $attributesInfo, + "simple_name" => $configurableChild->getName(), + "simple_sku" => $configurableChild->getSku(), + ]; + $simpleBuyRequest = [ + "info_buyRequest" => [ + "uenc" => "aHR0cDovL21hZ2VudG8uZGV2L2NvbmZpZ3VyYWJsZS1wcm9kdWN0LTEuaHRtbA,,", + "product" => $configurableId, + "selected_configurable_option" => $simpleId, + "related_product" => "", + "super_attribute" => $superAttribute, + "qty" => "1" + ] + ]; + + $quoteConfigurableBuyRequest = $configurableBuyRequest['info_buyRequest']; + $quoteSimpleBuyRequest = $simpleBuyRequest['info_buyRequest']; + unset($quoteConfigurableBuyRequest['selected_configurable_option']); + unset($quoteSimpleBuyRequest['selected_configurable_option']); + + $productsResult[$key]['id'] = $configurableId; + $productsResult[$key]['sku'] = $simpleSku; + $productsResult[$key]['name'] = $configurableProduct->getName(); + $productsResult[$key]['childId'] = $simpleId; + $productsResult[$key]['buyRequest'] = [ + 'order' => $this->serializer->serialize($configurableBuyRequest), + 'quote' => $this->serializer->serialize($quoteConfigurableBuyRequest), + 'super_attribute' => $this->serializer->serialize($superAttribute) + ]; + $productsResult[$key]['childBuyRequest'] = [ + 'order' => $this->serializer->serialize($simpleBuyRequest), + 'quote' => $this->serializer->serialize($quoteSimpleBuyRequest), + ]; + + } + return $productsResult; + } + + /** + * Commit all active transactions at the end of the batch. + * + * Many transactions may exist, since generation process creates a transaction per each available DB connection. + * + * @return void + */ + private function commitBatch() + { + foreach ($this->resourceConnections as $connection) { + if ($connection->getTransactionLevel() > 0) { + $connection->commit(); + $connection->beginTransaction(); + } + } } /** @@ -294,12 +693,13 @@ public function getActionTitle() public function introduceParamLabels() { return [ - 'orders' => 'Orders' + 'orders' => 'Orders' ]; } /** - * Get real table name for db table, validated by db adapter + * Get real table name for db table, validated by db adapter. + * In case prefix or other features mutating default table names are used. * * @param string $tableName * @param string $resourceName @@ -307,19 +707,24 @@ public function introduceParamLabels() */ public function getTableName($tableName, $resourceName) { + /** @var \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource */ $resource = $this->fixtureModel->getObjectManager()->get($resourceName); - return $this->getConnection()->getTableName($resource->getTable($tableName)); + return $resource->getConnection()->getTableName($resource->getTable($tableName)); } /** - * Retrieve connection to resource specified by $resourceName + * Get sequence for order items * - * @return \Magento\Framework\DB\Adapter\AdapterInterface|false + * @param int $maxItemId + * @param int $requestedOrders + * @param int $maxItemsPerOrder + * @return \Generator */ - public function getConnection() + private function getItemIdSequence($maxItemId, $requestedOrders, $maxItemsPerOrder) { - return $this->fixtureModel->getObjectManager()->get( - \Magento\Framework\App\ResourceConnection::class - )->getConnection(); + $requestedItems = $requestedOrders * $maxItemsPerOrder; + for ($i = $maxItemId + 1; $i <= $requestedItems; $i++) { + yield $i; + } } } diff --git a/setup/src/Magento/Setup/Fixtures/PriceProvider.php b/setup/src/Magento/Setup/Fixtures/PriceProvider.php new file mode 100644 index 0000000000000..de091361884a1 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/PriceProvider.php @@ -0,0 +1,34 @@ +productCollectionFactory = $collectionFactory; + } + + /** + * Get amount of products filtered by specified pattern + * + * @param int $requestedProducts + * @param string $productSkuPattern + * @return int + */ + public function getAmount($requestedProducts, $productSkuPattern) + { + $productSkuPattern = str_replace('%s', '[0-9]+', $productSkuPattern); + $productCollection = $this->productCollectionFactory->create(); + $productCollection + ->getSelect() + ->where('sku ?', new \Zend_Db_Expr('REGEXP \'^' . $productSkuPattern . '$\'')); + + return max(0, $requestedProducts - $productCollection->getSize()); + } +} diff --git a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php index 6de4eaf91529f..b712238cfc47e 100644 --- a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php @@ -1,158 +1,391 @@ {products amount} + * Products will be distributed per Default and pre-defined + * attribute sets (@see setup/performance-toolkit/config/attributeSets.xml) + * + * If extra attribute set is specified in profile as: {sets amount} + * then products also will be distributed per additional attribute sets + * + * Products will be uniformly distributed per categories and websites + * If node "assign_entities_to_all_websites" from profile is set to "1" then products will be assigned to all websites + * + * @see setup/performance-toolkit/profiles/ce/small.xml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SimpleProductsFixture extends Fixture { + /** + * Simple product sku pattern + */ + const SKU_PATTERN = 'product_dynamic_%s'; + + /** + * @var int + */ + protected $priority = 31; + + /** + * @var array + */ + private $descriptionConfig; + + /** + * @var array + */ + private $shortDescriptionConfig; + + /** + * @var ProductFactory + */ + private $productFactory; + + /** + * @var ProductGenerator + */ + private $productGenerator; + /** * @var int */ - protected $priority = 30; + private $defaultAttributeSetId; + + /** + * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection + */ + private $attributeCollectionFactory; + + /** + * @var AttributeSetCollectionFactory + */ + private $attributeSetCollectionFactory; + + /** + * @var SearchTermDescriptionGeneratorFactory + */ + private $descriptionGeneratorFactory; + + /** + * @var ProductsAmountProvider + */ + private $productsAmountProvider; + + /** + * @var WebsiteCategoryProvider + */ + private $websiteCategoryProvider; + + /** + * @var PriceProvider + */ + private $priceProvider; + + /** + * @param FixtureModel $fixtureModel + * @param ProductFactory $productFactory + * @param ProductGenerator $productGenerator + * @param CollectionFactory $attributeCollectionFactory + * @param AttributeSetCollectionFactory $attributeSetCollectionFactory + * @param SearchTermDescriptionGeneratorFactory $descriptionGeneratorFactory + * @param WebsiteCategoryProvider $websiteCategoryProvider + * @param ProductsAmountProvider $productsAmountProvider + * @param PriceProvider $priceProvider + * @internal param FixtureConfig $fixtureConfig + */ + public function __construct( + FixtureModel $fixtureModel, + ProductFactory $productFactory, + ProductGenerator $productGenerator, + CollectionFactory $attributeCollectionFactory, + AttributeSetCollectionFactory $attributeSetCollectionFactory, + SearchTermDescriptionGeneratorFactory $descriptionGeneratorFactory, + WebsiteCategoryProvider $websiteCategoryProvider, + ProductsAmountProvider $productsAmountProvider, + PriceProvider $priceProvider + ) { + parent::__construct($fixtureModel); + $this->productFactory = $productFactory; + $this->productGenerator = $productGenerator; + $this->attributeCollectionFactory = $attributeCollectionFactory; + $this->attributeSetCollectionFactory = $attributeSetCollectionFactory; + $this->descriptionGeneratorFactory = $descriptionGeneratorFactory; + $this->productsAmountProvider = $productsAmountProvider; + $this->websiteCategoryProvider = $websiteCategoryProvider; + $this->priceProvider = $priceProvider; + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating simple products'; + } /** * {@inheritdoc} */ + public function introduceParamLabels() + { + return [ + 'simple_products' => 'Simple products' + ]; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD) + */ public function execute() { - $simpleProductsCount = $this->fixtureModel->getValue('simple_products', 0); + $simpleProductsCount = $this->productsAmountProvider->getAmount( + $this->fixtureModel->getValue('simple_products', 0), + $this->getSkuPattern() + ); + if (!$simpleProductsCount) { return; } - $this->fixtureModel->resetObjectManager(); - - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Category::class); - - $result = []; - //Get all websites - $websites = $storeManager->getWebsites(); - foreach ($websites as $website) { - $websiteCode = $website->getCode(); - //Get all groups - $websiteGroups = $website->getGroups(); - foreach ($websiteGroups as $websiteGroup) { - $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); - $category->load($websiteGroupRootCategory); - $categoryResource = $category->getResource(); - //Get all categories - $resultsCategories = $categoryResource->getAllChildren($category); - foreach ($resultsCategories as $resultsCategory) { - $category->load($resultsCategory); - $structure = explode('/', $category->getPath()); - $pathSize = count($structure); - if ($pathSize > 1) { - $path = []; - for ($i = 0; $i < $pathSize; $i++) { - $path[] = $category->load($structure[$i])->getName(); + + $defaultAttributeSets = $this->getDefaultAttributeSets(); + $searchTermsConfig = $this->getSearchTerms(); + + /** @var \Magento\Setup\Model\SearchTermDescriptionGenerator $descriptionGenerator */ + $descriptionGenerator = $this->descriptionGeneratorFactory->create( + $this->getDescriptionConfig(), + $searchTermsConfig, + $simpleProductsCount, + 'Full simple product Description %s' + ); + + /** @var \Magento\Setup\Model\SearchTermDescriptionGenerator $shortDescriptionGenerator */ + $shortDescriptionGenerator = $this->descriptionGeneratorFactory->create( + $this->getShortDescriptionConfig(), + $searchTermsConfig, + $simpleProductsCount, + 'Short simple product Description %s' + ); + + $additionalAttributeSets = $this->getAdditionalAttributeSets(); + $attributeSet = function ($index) use ($defaultAttributeSets, $additionalAttributeSets) { + mt_srand($index); + $attributeSetCount = count(array_keys($defaultAttributeSets)); + if ($attributeSetCount > (($index - 1) % (int)$this->fixtureModel->getValue('categories', 30))) { + return array_keys($defaultAttributeSets)[mt_rand(0, count(array_keys($defaultAttributeSets)) - 1)]; + } else { + $customSetsAmount = count($additionalAttributeSets); + return $customSetsAmount + ? $additionalAttributeSets[$index % count($additionalAttributeSets)]['attribute_set_id'] + : $this->getDefaultAttributeSetId(); + } + }; + + $additionalAttributes = function ( + $attributeSetId, + $index + ) use ( + $defaultAttributeSets, + $additionalAttributeSets + ) { + $attributeValues = []; + mt_srand($index); + if (isset($defaultAttributeSets[$attributeSetId])) { + foreach ($defaultAttributeSets[$attributeSetId] as $attributeCode => $values) { + $attributeValues[$attributeCode] = $values[mt_rand(0, count($values) - 1)]; + } + } + + return $attributeValues; + }; + + $fixtureMap = [ + 'name' => function ($productId) { + return sprintf('Simple Product %s', $productId); + }, + 'sku' => function ($productId) { + return sprintf($this->getSkuPattern(), $productId); + }, + 'price' => function ($index, $entityNumber) { + return $this->priceProvider->getPrice($entityNumber); + }, + 'url_key' => function ($productId) { + return sprintf('simple-product-%s', $productId); + }, + 'description' => function ($index) use ($descriptionGenerator) { + return $descriptionGenerator->generate($index); + }, + 'short_description' => function ($index) use ($shortDescriptionGenerator) { + return $shortDescriptionGenerator->generate($index); + }, + 'website_ids' => function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getWebsiteIds($index); + }, + 'category_ids' => function ($index, $entityNumber) { + return $this->websiteCategoryProvider->getCategoryId($index); + }, + 'attribute_set_id' => $attributeSet, + 'additional_attributes' => $additionalAttributes, + 'status' => function () { + return Status::STATUS_ENABLED; + } + ]; + $this->productGenerator->generate($simpleProductsCount, $fixtureMap); + } + + /** + * Get simple product sku pattern + * + * @return string + */ + private function getSkuPattern() + { + return self::SKU_PATTERN; + } + + /** + * Get default attribute set id + * + * @return int + */ + private function getDefaultAttributeSetId() + { + if (null === $this->defaultAttributeSetId) { + $this->defaultAttributeSetId = (int)$this->productFactory->create()->getDefaultAttributeSetId(); + } + + return $this->defaultAttributeSetId; + } + + /** + * Get default attribute sets with attributes + * + * @see config/attributeSets.xml + * @return array + */ + private function getDefaultAttributeSets() + { + $attributeSets = $this->fixtureModel->getValue('attribute_sets', null); + $attributes = []; + + if ($attributeSets !== null && array_key_exists('attribute_set', $attributeSets)) { + foreach ($attributeSets['attribute_set'] as $attributeSet) { + $attributesData = array_key_exists(0, $attributeSet['attributes']['attribute']) + ? $attributeSet['attributes']['attribute'] : [$attributeSet['attributes']['attribute']]; + + $attributeCollection = $this->attributeCollectionFactory->create(); + + $attributeCollection->setAttributeSetFilterBySetName($attributeSet['name'], Product::ENTITY); + $attributeCollection->addFieldToFilter( + 'attribute_code', + array_column($attributesData, 'attribute_code') + ); + /** @var \Magento\Eav\Model\Entity\Attribute $attribute */ + foreach ($attributeCollection as $attribute) { + $values = []; + $options = $attribute->getOptions(); + foreach (($options ?: []) as $option) { + if ($option->getValue()) { + $values[] = $option->getValue(); } - array_shift($path); - $resultsCategoryName = implode('/', $path); - } else { - $resultsCategoryName = $category->getName(); - } - //Deleted root categories - if (trim($resultsCategoryName) != '') { - $result[$resultsCategory] = [$websiteCode, $resultsCategoryName]; } + $attributes[$attribute->getAttributeSetId()][$attribute->getAttributeCode()] = $values; } } } - $result = array_values($result); + $attributes[$this->getDefaultAttributeSetId()] = []; - $productWebsite = function ($index) use ($result) { - return $result[$index % count($result)][0]; - }; - $productCategory = function ($index) use ($result) { - return $result[$index % count($result)][1]; - }; + return $attributes; + } - $generator = new Generator( - $this->getPattern($productWebsite, $productCategory), - $simpleProductsCount - ); - /** @var \Magento\ImportExport\Model\Import $import */ - $import = $this->fixtureModel->getObjectManager()->create( - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'catalog_product', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors' - ] - ] - ); - // it is not obvious, but the validateSource() will actually save import queue data to DB - $import->validateSource($generator); - // this converts import queue into actual entities - $import->importSource(); + /** + * Get search terms config which used for product description generation + * + * @return array + */ + private function getSearchTerms() + { + $searchTerms = $this->fixtureModel->getValue('search_terms', []); + if (!empty($searchTerms)) { + $searchTerms = array_key_exists(0, $searchTerms['search_term']) + ? $searchTerms['search_term'] : [$searchTerms['search_term']]; + } + + return $searchTerms; } /** - * Get pattern for product import + * Get description config * - * @param Closure|int|string $productWebsiteClosure - * @param Closure|int|string $productCategoryClosure * @return array */ - protected function getPattern($productWebsiteClosure, $productCategoryClosure) + private function getDescriptionConfig() { - return [ - 'attribute_set_code' => 'Default', - 'product_type' => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, - 'product_websites' => $productWebsiteClosure, - 'categories' => $productCategoryClosure, - 'name' => 'Simple Product %s', - 'short_description' => 'Short simple product description %s', - 'weight' => 1, - 'description' => 'Full simple product Description %s', - 'sku' => 'product_dynamic_%s', - 'price' => 10, - 'visibility' => 'Catalog, Search', - 'product_online' => \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, - 'tax_class_name' => 'Taxable Goods', - /** - * actually it saves without stock data, but by default system won't show on the - * frontend products out of stock - */ - 'is_in_stock' => 1, - 'qty' => 100500, - 'out_of_stock_qty' => 'Use Config', - 'allow_backorders' => 'Use Config', - 'min_cart_qty' => 'Use Config', - 'max_cart_qty' => 'Use Config', - 'notify_on_stock_below' => 'Use Config', - 'manage_stock' => 'Use Config', - 'qty_increments' => 'Use Config', - 'enable_qty_incremements' => 'Use Config', - ]; + if (null === $this->descriptionConfig) { + $this->descriptionConfig = $this->readDescriptionConfig('description'); + } + + return $this->descriptionConfig; } /** - * {@inheritdoc} + * Get short description config + * + * @return array */ - public function getActionTitle() + private function getShortDescriptionConfig() { - return 'Generating simple products'; + if (null === $this->shortDescriptionConfig) { + $this->shortDescriptionConfig = $this->readDescriptionConfig('short-description'); + } + + return $this->shortDescriptionConfig; } /** - * {@inheritdoc} + * Get description config from fixture + * + * @param string $configSrc + * @return array */ - public function introduceParamLabels() + private function readDescriptionConfig($configSrc) { - return [ - 'simple_products' => 'Simple products' - ]; + $configData = $this->fixtureModel->getValue($configSrc, []); + + if (isset($configData['mixin']['tags'])) { + $configData['mixin']['tags'] = explode('|', $configData['mixin']['tags']); + } + + return $configData; + } + + /** + * Get additional attribute sets + * + * @return \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection[] + */ + private function getAdditionalAttributeSets() + { + /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection $sets */ + $sets = $this->attributeSetCollectionFactory->create(); + $sets->addFieldToFilter('attribute_set_name', ['like' => AttributeSetsFixture::PRODUCT_SET_NAME .'%']); + + return $sets->getData(); } } diff --git a/setup/src/Magento/Setup/Fixtures/StoresFixture.php b/setup/src/Magento/Setup/Fixtures/StoresFixture.php index dcd7dd5b6f50c..4103fdcafd534 100644 --- a/setup/src/Magento/Setup/Fixtures/StoresFixture.php +++ b/setup/src/Magento/Setup/Fixtures/StoresFixture.php @@ -1,161 +1,354 @@ {amount of websites} + * {amount of store groups} + * {amount of store views} + * {1|0} + * + * Each node of configuration except + * means how many entities need to be generated + * + * Store groups will have normal distribution among websites + * Store views will have normal distribution among store groups + * + * 1 + * means that all stores will have the same root category + * + * 1 + * means that all stores will have unique root category + * + * @see setup/performance-toolkit/profiles/ce/small.xml + * @SuppressWarnings(PHPMD) */ class StoresFixture extends Fixture { + + const DEFAULT_WEBSITE_COUNT = 1; + + const DEFAULT_STORE_COUNT = 1; + + const DEFAULT_STORE_VIEW_COUNT = 1; + /** * @var int */ protected $priority = 10; + /** + * @var array + */ + private $websiteIds = []; + + /** + * @var array + */ + private $storeGroupsIds = []; + + /** + * @var array + */ + private $storeGroupsToWebsites = []; + + /** + * @var StoreManager + */ + private $storeManager; + + /** + * @var Writer + */ + private $scopeConfig; + + /** + * @var Group + */ + private $defaultStoreGroup; + + /** + * @var Website + */ + private $defaultWebsite; + + /** + * @var int + */ + private $defaultParentCategoryId; + + /** + * @var int + */ + private $defaultStoreGroupId; + + /** + * @var int + */ + private $defaultWebsiteId; + + /** + * @var int + */ + private $storeGroupsCount; + + /** + * @var int + */ + private $storesCount; + + /** + * @var int + */ + private $websitesCount; + + /** + * @var bool + */ + private $singleRootCategory; + + /** + * @var StoreInterface + */ + private $defaultStoreView; + + /** + * @var int + */ + private $storeViewIds; + + /** + * @var ManagerInterface + */ + private $eventManager; + + /** + * @var CategoryFactory + */ + private $categoryFactory; + + /** + * @var Config + */ + private $localeConfig; + + /** + * StoresFixture constructor + * @param FixtureModel $fixtureModel + * @param StoreManager $storeManager + * @param ManagerInterface $eventManager + * @param CategoryFactory $categoryFactory + * @param Config $localeConfig + * @param Writer $scopeConfig + */ + public function __construct( + FixtureModel $fixtureModel, + StoreManager $storeManager, + ManagerInterface $eventManager, + CategoryFactory $categoryFactory, + Config $localeConfig, + Writer $scopeConfig + ) { + parent::__construct($fixtureModel); + $this->storeManager = $storeManager; + $this->eventManager = $eventManager; + $this->categoryFactory = $categoryFactory; + $this->localeConfig = $localeConfig; + $this->scopeConfig = $scopeConfig; + } + /** * {@inheritdoc} * @SuppressWarnings(PHPMD) */ public function execute() { - $websitesCount = $this->fixtureModel->getValue('websites', 0); - $storeGroupsCount = $this->fixtureModel->getValue('store_groups', 0); - $storesCount = $this->fixtureModel->getValue('store_views', 0); - if (!$websitesCount || !$storeGroupsCount || !$storesCount) { + //get settings counts + $this->websitesCount = $this->fixtureModel->getValue('websites', self::DEFAULT_WEBSITE_COUNT); + $this->storeGroupsCount = $this->fixtureModel->getValue('store_groups', self::DEFAULT_STORE_COUNT); + $this->storesCount = $this->fixtureModel->getValue('store_views', self::DEFAULT_STORE_VIEW_COUNT); + $this->singleRootCategory = (bool)$this->fixtureModel->getValue('assign_entities_to_all_websites', false); + + if ( + $this->websitesCount <= self::DEFAULT_WEBSITE_COUNT + && $this->storeGroupsCount <= self::DEFAULT_STORE_COUNT + && $this->storesCount <= self::DEFAULT_STORE_VIEW_COUNT + ) { return; } - $this->fixtureModel->resetObjectManager(); - - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - - /** @var $defaultWebsite \Magento\Store\Model\Website */ - $defaultWebsite = $storeManager->getWebsite(); - /** @var $defaultStoreGroup \Magento\Store\Model\Group */ - $defaultStoreGroup = $storeManager->getGroup(); - /** @var $defaultStoreView \Magento\Store\Model\Store */ - $defaultStoreView = $storeManager->getDefaultStoreView(); - - $defaultParentCategoryId = $storeManager->getStore()->getRootCategoryId(); - - $defaultWebsiteId = $defaultWebsite->getId(); - $defaultStoreGroupId = $defaultStoreGroup->getId(); - $defaultStoreViewId = $defaultStoreView->getId(); - - $websitesId = []; - $groupsId = []; - - //Create $websitesCount websites - for ($i = 0; $i < $websitesCount; $i++) { - $websiteId = null; - if ($i == 0) { - $websiteId = $defaultWebsiteId; - } - $website = clone $defaultWebsite; - $websiteCode = sprintf('website_%d', $i + 1); - $websiteName = sprintf('Website %d', $i + 1); + + //Get existing entities counts + $storeGroups = $this->storeManager->getGroups(); + $this->storeGroupsIds= array_keys($storeGroups); + + foreach ($storeGroups as $storeGroupId => $storeGroup) { + $this->storeGroupsToWebsites[$storeGroupId] = $storeGroup->getWebsiteId(); + } + + $this->websiteIds = array_values(array_unique($this->storeGroupsToWebsites)); + + $this->defaultWebsite = $this->storeManager->getWebsite(); + $this->defaultStoreGroup = $this->storeManager->getGroup(); + $this->defaultWebsiteId = $this->defaultWebsite->getId(); + $this->defaultStoreGroupId = $this->defaultStoreGroup->getId(); + $this->defaultStoreView = $this->storeManager->getDefaultStoreView(); + $this->storeViewIds = array_keys($this->storeManager->getStores()); + + $this->generateWebsites(); + $this->generateStoreGroups(); + $this->generateStoreViews(); + + //clean cache + $this->storeManager->reinitStores(); + } + + /** + * Generating web sites + * @return void + */ + private function generateWebsites() + { + $existedWebsitesCount = count($this->websiteIds) + self::DEFAULT_WEBSITE_COUNT; + + while ($existedWebsitesCount <= $this->websitesCount) { + $website = clone $this->defaultWebsite; + $websiteCode = sprintf('website_%d', $existedWebsitesCount); + $websiteName = sprintf('Website %d', $existedWebsitesCount); $website->addData( [ - 'website_id' => $websiteId, - 'code' => $websiteCode, - 'name' => $websiteName, - 'is_default' => (int)$i == 0, + 'website_id' => null, + 'code' => $websiteCode, + 'name' => $websiteName, + 'is_default' => false, ] ); $website->save(); - $websitesId[$i] = $website->getId(); - usleep(20); + $this->websiteIds[] = $website->getId(); + $existedWebsitesCount++; } + } - //Create $storeGroupsCount websites - $websiteNumber = 0; - for ($i = 0; $i < $storeGroupsCount; $i++) { - $category = $this->fixtureModel->getObjectManager()->create(\Magento\Catalog\Model\Category::class); - $websiteId = $websitesId[$websiteNumber]; - $groupId = null; - $categoryPath = '1'; - - $storeGroupName = sprintf('Store Group %d - website_id_%d', $i + 1, $websiteId); - - if ($i == 0 && $websiteId == $defaultWebsiteId) { - $groupId = $defaultStoreGroupId; - $categoryPath = '1/' . $defaultParentCategoryId; - $category->load($defaultParentCategoryId); - } + /** + * Generating store groups ('stores' on frontend) + * @return void + */ + private function generateStoreGroups() + { + $existedStoreGroupCount = count($this->storeGroupsIds); + $existedWebsitesCount = count($this->websiteIds); - $category->setName("Category $storeGroupName") - ->setPath($categoryPath) - ->setLevel(1) - ->setAvailableSortBy('name') - ->setDefaultSortBy('name') - ->setIsActive(true) - ->save(); + while ($existedStoreGroupCount < $this->storeGroupsCount) { + $websiteId = $this->websiteIds[$existedStoreGroupCount % $existedWebsitesCount]; + $storeGroupName = sprintf('Store Group %d - website_id_%d', ++$existedStoreGroupCount, $websiteId); - $storeGroup = clone $defaultStoreGroup; + $storeGroup = clone $this->defaultStoreGroup; $storeGroup->addData( [ - 'group_id' => $groupId, - 'website_id' => $websiteId, - 'name' => $storeGroupName, - 'root_category_id' => $category->getId(), + 'group_id' => null, + 'website_id' => $websiteId, + 'name' => $storeGroupName, + 'root_category_id' => $this->getStoreCategoryId($storeGroupName), ] ); $storeGroup->save(); - $groupsId[$websiteId][] = $storeGroup->getId(); - - $websiteNumber++; - if ($websiteNumber == count($websitesId)) { - $websiteNumber = 0; - } - usleep(20); + $this->storeGroupsIds[] = $storeGroup->getId(); + $this->storeGroupsToWebsites[$storeGroup->getId()] = $websiteId; } + } + + /** + * Generating store views + * @return void + */ + private function generateStoreViews() + { + $localesList = $this->localeConfig->getAllowedLocales(); + $localesListCount = count($localesList); + + $existedStoreViewsCount = count($this->storeViewIds); + $existedStoreGroupCount = count($this->storeGroupsIds); + + while ($existedStoreViewsCount < $this->storesCount) { + $groupId = $this->storeGroupsIds[$existedStoreViewsCount % $existedStoreGroupCount]; + $websiteId = $this->storeGroupsToWebsites[$groupId]; + $store = clone $this->defaultStoreView; + $storeCode = sprintf('store_view_%d', ++$existedStoreViewsCount); + $storeName = sprintf( + 'Store view %d - website_id_%d - group_id_%d', + $existedStoreViewsCount, + $websiteId, + $groupId + ); - //Create $storesCount stores - $websiteNumber = 0; - $groupNumber = 0; - for ($i = 0; $i < $storesCount; $i++) { - $websiteId = $websitesId[$websiteNumber]; - $groupId = $groupsId[$websiteId][$groupNumber]; - $storeId = null; - if ($i == 0 && $groupId == $defaultStoreGroupId) { - $storeId = $defaultStoreViewId; - } - $store = clone $defaultStoreView; - $storeCode = sprintf('store_view_%d_w_%d_g_%d', $i + 1, $websiteId, $groupId); - $storeName = sprintf('Store view %d - website_id_%d - group_id_%d', $i + 1, $websiteId, $groupId); $store->addData( [ - 'store_id' => $storeId, - 'name' => $storeName, - 'website_id' => $websiteId, - 'group_id' => $groupId, + 'store_id' => null, + 'name' => $storeName, + 'website_id' => $websiteId, + 'group_id' => $groupId, + 'code' => $storeCode ] - ); + )->save(); + $this->eventManager->dispatch('store_add', ['store' => $store]); - if ($storeId == null) { - $store->addData( - [ - 'code' => $storeCode, - ] - ); - } - - $store->save(); - - $groupNumber++; - if ($groupNumber == count($groupsId[$websiteId])) { - $groupNumber = 0; - $websiteNumber++; - if ($websiteNumber == count($websitesId)) { - $websiteNumber = 0; - } - } - usleep(20); + $this->saveStoreLocale($store->getId(), $localesList[$existedStoreViewsCount % $localesListCount]); + } + } + + /** + * @param int $storeId + * @param string $localeCode + * @return void + */ + private function saveStoreLocale($storeId, $localeCode) + { + $this->scopeConfig->save( + \Magento\Directory\Helper\Data::XML_PATH_DEFAULT_LOCALE, + $localeCode, + \Magento\Store\Model\ScopeInterface::SCOPE_STORES, + $storeId + ); + } + + /** + * Getting category id for store + * + * @param string $storeGroupName + * @return int + */ + private function getStoreCategoryId($storeGroupName) + { + if ($this->singleRootCategory) { + return $this->getDefaultCategoryId(); + } else { + //Generating category for store + $category = $this->categoryFactory->create(); + $categoryPath = Category::TREE_ROOT_ID; + $category->setName("Category " . $storeGroupName) + ->setPath($categoryPath) + ->setLevel(1) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->save(); + + return $category->getId(); } } @@ -173,9 +366,22 @@ public function getActionTitle() public function introduceParamLabels() { return [ - 'websites' => 'Websites', - 'store_groups' => 'Store Groups', - 'store_views' => 'Store Views' + 'websites' => 'Websites', + 'store_groups' => 'Store Groups Count', + 'store_views' => 'Store Views Count' ]; } + + /** + * Get default category id + * + * @return int + */ + private function getDefaultCategoryId() + { + if (null === $this->defaultParentCategoryId) { + $this->defaultParentCategoryId = $this->storeManager->getStore()->getRootCategoryId(); + } + return $this->defaultParentCategoryId; + } } diff --git a/setup/src/Magento/Setup/Fixtures/TaxRatesFixture.php b/setup/src/Magento/Setup/Fixtures/TaxRatesFixture.php index e024b47bc568a..baa5209ab952c 100644 --- a/setup/src/Magento/Setup/Fixtures/TaxRatesFixture.php +++ b/setup/src/Magento/Setup/Fixtures/TaxRatesFixture.php @@ -1,6 +1,6 @@ fixtureModel->resetObjectManager(); /** Clean predefined tax rates to maintain consistency */ - /** @var $collection Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection */ + /** @var $collection \Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection */ $collection = $this->fixtureModel->getObjectManager() ->get(\Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection::class); - /** @var $model Magento\Tax\Model\Calculation\Rate */ + /** @var $model \Magento\Tax\Model\Calculation\Rate */ $model = $this->fixtureModel->getObjectManager() ->get(\Magento\Tax\Model\Calculation\Rate::class); @@ -42,7 +42,7 @@ public function execute() /** * Import tax rates with import handler */ - $filename = realpath(__DIR__ . '/' . $taxRatesFile); + $filename = realpath(__DIR__ . DIRECTORY_SEPARATOR . "_files" . DIRECTORY_SEPARATOR . $taxRatesFile); $file = [ 'name' => $filename, 'type' => 'fixtureModel/vnd.ms-excel', diff --git a/setup/src/Magento/Setup/Fixtures/TaxRulesFixture.php b/setup/src/Magento/Setup/Fixtures/TaxRulesFixture.php new file mode 100644 index 0000000000000..a60145940953b --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/TaxRulesFixture.php @@ -0,0 +1,226 @@ + [ + Config::CONFIG_XML_PATH_SHIPPING_INCLUDES_TAX => 1, + Config::CONFIG_XML_PATH_PRICE_INCLUDES_TAX => 1, + Config::CONFIG_XML_PATH_APPLY_AFTER_DISCOUNT => 1, + Config::XML_PATH_DISPLAY_SALES_PRICE => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_SALES_SUBTOTAL => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_SALES_SHIPPING => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_SALES_DISCOUNT => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_SALES_GRANDTOTAL => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_SALES_FULL_SUMMARY => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::CONFIG_XML_PATH_DISPLAY_SHIPPING => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::CONFIG_XML_PATH_PRICE_DISPLAY_TYPE => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_CART_PRICE => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_CART_SUBTOTAL => Config::DISPLAY_TYPE_INCLUDING_TAX, + Config::XML_PATH_DISPLAY_CART_SHIPPING => Config::DISPLAY_TYPE_INCLUDING_TAX, + Custom::XML_PATH_TAX_WEEE_ENABLE => 1, + ] + ]; + + /** + * @var int + */ + protected $priority = 101; + + /** + * @var TaxRuleRepositoryInterface + */ + private $taxRuleRepository; + + /** + * @var TaxRuleInterfaceFactory + */ + private $taxRuleFactory; + + /** + * @var TaxRateInterfaceFactory + */ + private $taxRateFactory; + + /** + * @var CollectionFactory + */ + private $taxRateCollectionFactory; + + /** + * @var TaxRateRepositoryInterface + */ + private $taxRateRepository; + + /** + * @var ConfigWriter + */ + private $configWriter; + + /** + * @param FixtureModel $fixtureModel + * @param TaxRuleRepositoryInterface $taxRuleRepository + * @param TaxRuleInterfaceFactory $taxRuleFactory + * @param CollectionFactory $taxRateCollectionFactory + * @param TaxRateInterfaceFactory $taxRateFactory + * @param TaxRateRepositoryInterface $taxRateRepository + * @param ConfigWriter $configWriter + */ + public function __construct( + FixtureModel $fixtureModel, + TaxRuleRepositoryInterface $taxRuleRepository, + TaxRuleInterfaceFactory $taxRuleFactory, + CollectionFactory $taxRateCollectionFactory, + TaxRateInterfaceFactory $taxRateFactory, + TaxRateRepositoryInterface $taxRateRepository, + ConfigWriter $configWriter + ) { + parent::__construct($fixtureModel); + + $this->taxRuleRepository = $taxRuleRepository; + $this->taxRuleFactory = $taxRuleFactory; + $this->taxRateCollectionFactory = $taxRateCollectionFactory; + $this->taxRateFactory = $taxRateFactory; + $this->taxRateRepository = $taxRateRepository; + $this->configWriter = $configWriter; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + //Getting config values + $taxMode = $this->fixtureModel->getValue('tax_mode', null); + $taxRules = $this->fixtureModel->getValue('tax_rules', 0); + + if ($taxMode && in_array($taxMode, array_keys($this->configs))) { + $this->setTaxMode($taxMode); + } + + $taxRateIds = $this->taxRateCollectionFactory->create()->getAllIds(); + $taxRatesCount = count($taxRateIds); + + while ($taxRules) { + /** @var $taxRuleDataObject TaxRuleInterface */ + $taxRuleDataObject = $this->taxRuleFactory->create(); + $taxRuleDataObject->setCode('Tax_Rule_' . $taxRules) + ->setTaxRateIds([$taxRateIds[$taxRules % $taxRatesCount]]) + ->setCustomerTaxClassIds([self::DEFAULT_CUSTOMER_TAX_CLASS_ID]) + ->setProductTaxClassIds([self::DEFAULT_PRODUCT_TAX_CLASS_ID]) + ->setPriority(0) + ->setPosition(0); + + $this->taxRuleRepository->save($taxRuleDataObject); + + $taxRules--; + } + } + + /** + * Adding appropriate Tax Rate, Tax Rule and Config Settings for selected Tax Mode (for example EU/VAT) + * + * @param string $taxMode + * @return void + */ + private function setTaxMode($taxMode) + { + //Add Tax Rate for selected Tax Mode + /** @var $taxRate TaxRateInterface */ + $taxRate = $this->taxRateFactory->create(); + $taxRate->setCode($taxMode) + ->setRate(self::DEFAULT_TAX_RATE) + ->setTaxCountryId(self::DEFAULT_TAX_COUNTRY) + ->setTaxPostcode('*'); + + $taxRateData = $this->taxRateRepository->save($taxRate); + + //Add Tax Rule for Tax Mode + /** @var $taxRuleDataObject TaxRuleInterface */ + $taxRuleDataObject = $this->taxRuleFactory->create(); + $taxRuleDataObject->setCode($taxMode) + ->setTaxRateIds([$taxRateData->getId()]) + ->setCustomerTaxClassIds([self::DEFAULT_CUSTOMER_TAX_CLASS_ID]) + ->setProductTaxClassIds([self::DEFAULT_PRODUCT_TAX_CLASS_ID]) + ->setPriority(0) + ->setPosition(0); + + $this->taxRuleRepository->save($taxRuleDataObject); + + //Set Tax Mode configs + $this->setConfigByTaxMode($taxMode); + } + + /** + * Set appropriate Tax Config Settings for selected Tax Mode + * + * @param string $mode + * @return void + */ + private function setConfigByTaxMode($mode = self::DEFAULT_TAX_MODE) + { + if (isset($this->configs[$mode]) && is_array($this->configs[$mode])) { + foreach ($this->configs[$mode] as $configPath => $value) { + $this->configWriter->save( + $configPath, + $value + ); + } + } + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating tax rules'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'tax_rules' => 'Tax Rules Count', + 'tax_mode' => 'Tax Mode', + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/WebsiteCategoryProvider.php b/setup/src/Magento/Setup/Fixtures/WebsiteCategoryProvider.php new file mode 100644 index 0000000000000..e96ec9ce57c98 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/WebsiteCategoryProvider.php @@ -0,0 +1,155 @@ +fixtureConfig = $fixtureConfig; + $this->resourceConnection = $resourceConnection; + } + + /** + * Get websites for $productIndex product + * + * @param int $productIndex Index of generated product + * @return array + */ + public function getWebsiteIds($productIndex) + { + if ($this->isAssignToAllWebsites()) { + return $this->getAllWebsites(); + } else { + $categoriesPerWebsite = $this->getCategoriesAndWebsites(); + return [$categoriesPerWebsite[$productIndex % count($categoriesPerWebsite)]['website']]; + } + } + + /** + * Get product if for $productIndex product + * + * @param int $productIndex + * @return int + */ + public function getCategoryId($productIndex) + { + if ($this->isAssignToAllWebsites()) { + $categories = $this->getAllCategories(); + return $categories[$productIndex % count($categories)]; + } else { + $categoriesPerWebsite = $this->getCategoriesAndWebsites(); + return $categoriesPerWebsite[$productIndex % count($categoriesPerWebsite)]['category']; + } + } + + /** + * @return array + */ + private function getCategoriesAndWebsites() + { + if (null === $this->categoriesPerWebsite) { + $select = $this->getConnection()->select() + ->from( + ['c' => $this->resourceConnection->getTableName('catalog_category_entity')], + ['category' => 'entity_id'] + )->join( + ['sg' => $this->resourceConnection->getTableName('store_group')], + "c.path like concat('1/', sg.root_category_id, '/%')", + ['website' => 'website_id'] + ); + $this->categoriesPerWebsite = $this->getConnection()->fetchAll($select); + } + + return $this->categoriesPerWebsite; + } + + /** + * @return bool + */ + private function isAssignToAllWebsites() + { + return (bool)$this->fixtureConfig->getValue('assign_entities_to_all_websites', false); + } + + /** + * @return array + */ + private function getAllWebsites() + { + if (null === $this->websites) { + $this->websites = array_unique(array_column($this->getCategoriesAndWebsites(), 'website')); + } + + return $this->websites; + } + + /** + * @return array + */ + private function getAllCategories() + { + if (null === $this->categories) { + $this->categories = array_values(array_unique(array_column($this->getCategoriesAndWebsites(), 'category'))); + } + + return $this->categories; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (null === $this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + + return $this->connection; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/_files/dictionary.csv b/setup/src/Magento/Setup/Fixtures/_files/dictionary.csv new file mode 100644 index 0000000000000..c839b7c46f51c --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/_files/dictionary.csv @@ -0,0 +1,7373 @@ +the +of +and +a +in +to +it +is +to +was +I +for +that +you +he +be +with +on +by +at +have +are +not +this +but +had +they +his +from +she +that +which +or +we +an +were +as +do +been +their +has +would +there +what +will +all +if +can +her +said +who +one +so +up +as +them +some +when +could +him +into +its +then +two +out +time +my +about +did +your +now +me +no +other +only +just +more +these +also +people +know +any +first +see +very +new +may +well +should +like +than +how +get +way +one +our +made +got +after +think +between +many +years +er +those +go +being +because +down +yeah +three +good +back +make +such +on +there +through +year +over +must +still +even +take +too +more +here +own +come +last +does +oh +say +no +going +work +where +erm +us +government +same +man +might +day +yes +however +put +world +over +another +in +want +as +life +most +against +again +never +under +old +much +something +Mr +why +each +while +house +part +number +found +off +different +went +really +thought +came +used +children +always +four +where +without +give +few +within +about +system +local +place +great +during +although +small +before +look +next +when +case +end +things +social +most +find +group +quite +mean +five +party +every +company +women +says +important +took +much +men +information +both +national +often +seen +given +school +fact +money +told +away +high +point +night +state +business +second +British +need +taken +done +right +having +thing +looked +London +area +perhaps +head +water +right +family +long +hand +like +already +possible +nothing +yet +large +left +side +asked +set +whether +days +mm +home +called +John +development +week +use +country +power +later +almost +young +council +himself +far +both +use +room +together +tell +little +political +before +able +become +six +general +service +eyes +members +since +times +problem +anything +market +towards +court +public +others +face +full +doing +war +car +felt +police +keep +held +problems +road +probably +help +interest +available +law +best +form +looking +early +making +today +mother +saw +knew +education +work +actually +policy +ever +so +at least +office +am +research +feel +big +body +door +let +Britain +name +person +services +months +report +question +using +health +turned +lot +million +main +though +words +enough +child +less +book +period +until +several +sure +father +level +control +known +society +major +seemed +around +began +itself +themselves +minister +economic +wanted +upon +areas +after +therefore +woman +England +city +community +only +including +centre +gave +job +among +position +effect +likely +real +clear +staff +black +kind +read +provide +particular +became +line +moment +international +action +special +difficult +certain +particularly +either +open +management +taking +across +idea +further +whole +age +process +act +around +evidence +view +better +off +mind +sense +rather +seems +believe +morning +third +else +half +white +death +sometimes +thus +brought +getting +ten +shall +try +behind +heard +table +change +support +back +sort +Mrs +whose +industry +ago +free +care +order +century +range +European +gone +yesterday +training +working +ask +street +home +word +groups +history +central +all +study +usually +remember +trade +hundred +programme +food +committee +air +hours +experience +rate +hands +indeed +sir +language +land +result +course +someone +everything +certainly +based +team +section +leave +trying +coming +similar +once +minutes +authority +human +changes +little +cases +common +role +Europe +necessary +nature +class +reason +long +saying +town +show +subject +voice +companies +since +simply +especially +department +single +short +personal +pay +value +member +started +run +patients +paper +private +seven +UK +eight +systems +herself +practice +wife +price +type +seem +figure +former +lost +right +need +matter +decision +bank +countries +until +makes +union +terms +financial +needed +south +university +club +president +friend +parents +quality +cos +building +north +stage +meeting +foreign +soon +strong +situation +comes +late +bed +recent +date +low +US +concerned +girl +hard +American +David +according to +twenty +higher +tax +production +various +understand +led +bring +schools +ground +conditions +secretary +weeks +clearly +bad +art +start +include +poor +hospital +friends +decided +shown +music +month +English +tried +game +1990 +May +anyone +wrong +ways +chapter +followed +cost +play +present +love +issue +goes +described +award +Mr. +king +royal +results +workers +April +expected +amount +students +despite +knowledge +June +moved +news +light +March +approach +cut +basis +hair +required +further +paid +series +better +before +field +allowed +easy +kept +questions +natural +live +future +rest +project +greater +feet +meet +simple +died +for +happened +added +manager +computer +security +near +met +evening +means +round +carried +hear +bit +heart +forward +sent +above +attention +labour +story +structure +move +agreed +nine +letter +individual +force +studies +movement +account +per +call +board +success +1989 +French +following +considered +current +everyone +fire +agreement +please +boy +capital +stood +analysis +whatever +population +modern +theory +books +stop +legal +Scotland +material +son +received +model +chance +environment +finally +performance +sea +rights +growth +authorities +provided +nice +whom +produced +relationship +talk +turn +built +final +east +1991 +talking +fine +worked +west +parties +size +record +red +close +property +myself +example +space +giving +normal +nor +reached +buy +serious +quickly +Peter +along +plan +behaviour +France +recently +term +previous +couple +included +pounds +anyway +cup +treatment +energy +total +thank +director +prime +levels +significant +issues +sat +income +top +choice +costs +design +pressure +scheme +July +change +a bit +list +suddenly +continue +technology +hall +takes +ones +details +happy +consider +won +defence +following +parts +loss +industrial +activities +throughout +spent +outside +teachers +generally +opened +floor +round +activity +hope +points +association +nearly +United +allow +rates +sun +army +sorry +wall +hotel +forces +contract +dead +Paul +stay +reported +as well +hour +difference +meant +summer +county +specific +numbers +wide +appropriate +husband +top +played +relations +Dr +figures +chairman +set +lower +product +colour +ideas +George +St +look +arms +obviously +unless +produce +changed +season +developed +unit +appear +investment +test +basic +write +village +reasons +military +original +successful +garden +effects +aware +yourself +exactly +help +suppose +showed +style +employment +passed +appeared +page +hold +suggested +Germany +continued +October +offered +products +popular +science +New +window +expect +beyond +resources +rules +professional +announced +economy +picture +okay +needs +doctor +maybe +events +direct +gives +advice +running +circumstances +sales +risk +interests +September +dark +event +thousand +involved +written +park +1988 +returned +ensure +America +fish +wish +opportunity +commission +1992 +oil +sound +ready +lines +shop +looks +James +immediately +worth +college +press +January +fell +blood +goods +playing +carry +less +film +prices +useful +conference +operation +follows +extent +designed +application +station +television +access +Richard +response +degree +majority +effective +established +wrote +region +green +York +ah +western +traditional +easily +cold +shows +offer +though +statement +Scottish +published +forms +German +down +accept +miles +independent +election +support +importance +lady +site +jobs +needs +plans +earth +earlier +title +parliament +standards +leaving +interesting +houses +planning +considerable +girls +involved +Ireland +increase +species +stopped +concern +public +means +caused +raised +through +glass +physical +thought +Michael +eye +left +heavy +walked +daughter +existing +competition +speak +responsible +up to +river +follow +software +complete +above +November +December +purpose +mouth +medical +responsibility +Sunday +Wales +leader +tomorrow +piece +thirty +lay +officer +task +blue +answer +stand +thinking +extra +highly +places +arm +eventually +campaign +ability +appeal +whole +Charles +skills +opposition +remained +pattern +method +miss +hot +lead +source +bought +baby +lack +once +bill +division +remain +surface +older +charge +methods +trouble +fully +equipment +moving +suggest +disease +officers +past +peace +male +slightly +demand +failed +wants +attempt +types +Christmas +hit +post +policies +hardly +ii +arrived +compared +below +otherwise +windows +West +deal +directly +interested +sale +like +firm +status +happen +box +even if +teacher +radio +provision +variety +show +ran +sector +return +factors +essential +direction +beautiful +civil +base +waiting +caught +sit +develop +character +safety +placed +past +completely +tea +introduced +killed +love +mum +context +fifty +primary +animals +culture +Oxford +brother +obvious +weight +discussion +created +1987 +future +other +start +States +none +sold +let's +machine +afternoon +knows +environmental +fair +William +February +provides +wait +league +trees +positive +organisation +win +condition +families +argument +Saturday +learn +up +normally +claimed +truth +senior +kitchen +works +add +lived +library +minute +believed +enough +transport +share +principle +create +agree +born +players +cash +exchange +rule +budget +turn +pupils +nuclear +sitting +version +English +best +features +duty +annual +balance +front +send +boys +presence +protection +dog +courses +individuals +matters +media +avoid +influence +presented +speaker +stone +relevant +apply +August +explain +deep +Robert +1986 +achieved +slowly +relatively +shares +letters +finished +survey +huge +accepted +covered +review +Smith +closed +form +marriage +commercial +aid +lives +collection +living +speech +Africa +regional +differences +benefit +apparently +effort +gets +executive +later +latter +function +failure +return +chair +reference +horse +becomes +attack +reports +practical +queen +subjects +career +bar +official +text +appears +separate +student +names +sell +holiday +larger +cells +open +progress +early +states +helped +visit +smiled +stock +memory +merely +studio +key +putting +eat +opinion +understanding +regular +decisions +chief +drawn +firms +remains +facilities +values +district +cars +due +mhm +begin +managed +receive +corner +image +edge +sister +politics +expression +instead +impact +quarter +forced +inside +views +scale +plant +race +ball +gold +join +Henry +spend +voice +alone +additional +benefits +1985 +trust +for instance +largely +advantage +associated +increased +standing +dad +foot +somebody +pain +gas +clothes +smaller +aspects +active +affairs +possibly +increase +railway +ended +feeling +network +leaders +nevertheless +cause +half +powerful +step +complex +joined +plants +standard +holding +carefully +length +mind +rise +strength +crime +hard +wind +Mary +possibility +becoming +damage +records +reduce +examples +mainly +credit +winter +impossible +insurance +explained +units +currently +forest +formed +somewhere +earlier +beginning +regarded +fall +confidence +discussed +speed +legislation +mentioned +along +pulled +spoke +debate +intended +bodies +message +middle +plus +supply +100 +skin +Edward +stuff +providing +entirely +front +domestic +require +proved +expressed +treated +match +solution +previously +tonight +patient +actual +difficulties +farm +united +far +build +reach +proposals +extremely +choose +ministers +technical +fresh +ordinary +scene +materials +museum +Thomas +move +article +prevent +achieve +customers +includes +powers +band +items +justice +play +animal +internal +suggests +excellent +face +rich +assessment +save +phone +fairly +football +watched +telephone +steps +decide +South +traffic +watch +coffee +deal +sources +past +buildings +increasingly +relief +distance +introduction +forty +administration +no +safe +applied +sight +Mark +island +potential +banks +housing +meaning +existence +claim +northern +enjoy +reduced +twelve +equally +in front of +walk +very +apart from +watching +cultural +famous +latest +users +TV +cabinet +legs +institutions +Japan +measures +reality +proper +video +worse +lose +argued +train +spirit +programmes +accounts +trial +target +fear +joint +doubt +formal +unemployment +prison +accident +concept +limited +elements +strange +served +papers +discovered +conservative +rock +cover +usual +tree +smile +unable +warm +surely +organization +battle +proportion +difficulty +sides +refused +weekend +construction +picked +distribution +dinner +wine +while +works +obtained +exercise +writing +asking +showing +ahead +rural +lovely +applications +twice +factor +path +games +funds +whereas +nobody +shape +initial +substantial +referred +tend +seat +improve +onto +thanks +aircraft +light +contact +quiet +rain +background +identified +contrast +officials +strategy +average +master +forget +leading +soft +reasonable +seeing +pound +grounds +raise +immediate +communication +client +Paris +star +fourth +suitable +determined +ought +detail +everybody +noted +equal +imagine +appointed +manner +homes +classes +freedom +operations +detailed +keeping +selection +requirements +pair +draw +walls +talks +working +call +danger +attitude +user +overall +offer +female +relationships +Edinburgh +note +afraid +pick +charges +democratic +elections +entered +courts +growing +goal +straight +techniques +sufficient +middle +agency +scientific +eastern +crisis +rose +correct +removed +prince +theatre +Irish +laid +act +expensive +markets +sign +educational +capacity +telling +happens +absolutely +patterns +whilst +managers +purposes +employees +1984 +totally +opportunities +cause +break +will +procedure +feeling +output +mental +frequently +bridge +dangerous +either +fingers +recognition +largest +turning +arrangements +sites +profits +quick +absence +sentence +beside +pass +fields +critical +pointed +prove +listen +inc +recorded +cost +signed +hill +dropped +card +tour +understood +notes +track +1983 +partly +replaced +increased +weather +principles +seriously +familiar +related +package +elsewhere +teaching +bottom +necessarily +commitment +player +double +birds +properly +1993 +Jack +threat +notice +unlikely +admitted +1981 +replied +silence +route +file +liked +supported +issued +perfect +victory +discuss +widely +occur +second +violence +efforts +element +neck +carrying +conflict +pieces +Darlington +under +profit +reaction +colleagues +historical +standard +end +Friday +finance +hope +rooms +projects +closely +fund +daily +below +cell +Liverpool +Tom +southern +expenditure +increasing +discipline +completed +occurred +individual +spring +audience +lead +thousands +grow +conversation +tiny +congress +emphasis +finding +exist +check +alone +consideration +speaking +learning +defined +seek +1979 +appearance +maintain +option +dry +bright +urban +pictures +estate +debt +youth +neither +affected +married +feature +payment +exhibition +liberal +supposed +assembly +reform +empty +boat +suffered +bus +hell +remembered +driver +lunch +flowers +heat +processes +upper +volume +share +captain +murder +North +fifteen +represented +meetings +contribution +drugs +die +feelings +outside +Ian +arts +leg +serve +dealing +writing +curriculum +bag +sought +apparent +branch +beginning +noticed +procedures +models +Martin +enter +revealed +institute +establish +object +occasion +waste +facts +membership +requires +shook +Monday +claims +control +prepared +younger +faith +shops +challenge +answer +Russian +moral +pleasure +orders +Alan +heads +bloody +careful +filled +Corp +literature +birth +1980 +leading +code +centres +broke +prepared +that +professor +1982 +aye +wood +gentleman +flight +entry +pretty +attractive +wild +investigation +crown +protect +nodded +greatest +subject to +functions +encourage +belief +care +developments +description +tradition +Japanese +thin +adopted +vital +document +conclusion +hoped +Italy +enjoyed +engineering +coal +transfer +address +breath +along with +Ltd +alternative +total +schemes +copy +desire +search +effectively +organisations +demands +pushed +visit +etc +planning +farmers +ancient +released +opening +lips +iii +treaty +newspaper +aim +drug +identify +engine +Manchester +USA +tests +owner +sky +Tony +wearing +depends +elderly +ministry +Australia +busy +inside +anybody +reading +external +capable +marketing +streets +partner +respect +shot +institution +generation +acid +realised +chosen +wider +his +narrow +horses +broad +ordered +wonderful +key +contained +laughed +bringing +clients +typical +drink +Stephen +employed +atmosphere +slow +wondered +clean +actions +entire +troops +Leeds +vote +definition +welfare +reduction +row +walking +laws +visitors +release +meanwhile +confirmed +examination +doors +leadership +attitudes +East +enable +beneath +journey +milk +stated +hence +IBM +machines +affect +grey +screen +criticism +surprise +reading +nineteen +stories +billion +constant +teeth +brain +explanation +brief +signs +married +highest +cover +starting +knowing +claim +creation +castle +governments +goals +intention +India +vast +flat +guide +drive +surprised +easier +ideal +shut +readers +run +Bill +magazine +bound +terrible +thoughts +kinds +academic +worry +minor +seats +customer +significance +measure +pleased +unfortunately +o'clock +revolution +attempts +noise +charged +rare +biggest +rather than +somewhat +sections +stared +seeking +paying +meeting +encouraged +thick +Jones +loved +metal +grand +plenty +note +phase +coast +injury +China +granted +motion +observed +technique +ill +drew +potential +factory +lying +severe +mine +lights +wonder +Harry +spread +contains +strongly +offers +afterwards +committed +tape +shoulder +bear +corporate +obtain +kill +that is +worst +learned +settlement +ooh +grew +represent +rapidly +tall +hole +living +adult +iron +amongst +faced +negative +afford +lots +index +permanent +beat +trip +contain +fundamental +doctors +desk +ourselves +sport +unions +implications +fashion +content +similarly +elected +proposed +judge +pool +inflation +brown +Brian +originally +funny +via +practices +somehow +payments +odd +Andrew +pension +pay +crucial +fit +inner +appointment +used +flow +launched +Chris +independence +Spain +objects +setting +little +least +colours +palace +perfectly +combination +contracts +criminal +consequences +pages +contemporary +UN +talked +session +sharp +structures +planned +drive +Wednesday +Kingdom +falling +sample +virtually +fast +sick +movements +dogs +Anne +Yorkshire +Roman +accommodation +nation +temperature +massive +societies +consumer +cities +offices +documents +valley +indicated +breakfast +stayed +kids +display +named +bedroom +sports +aspect +unique +Steve +sixty +author +lane +objectives +secondary +wear +republic +agent +interpretation +assistance +directors +badly +alright +parliamentary +African +Joe +unknown +industries +assets +selling +moreover +Northern +nose +Jim +Russia +subsequent +place +describe +declared +gallery +allowing +ship +other than +visited +cross +grown +crowd +recognised +interview +broken +Simon +argue +BBC +naturally +thinking +general +Mike +meal +catch +representatives +proceedings +tears +alive +involving +shoulders +employers +begun +departments +vision +yours +unix +beauty +guilty +proposal +impression +square +angry +regulations +regions +vehicle +Jane +democracy +sequence +offering +Graham +enormous +invited +cancer +sheet +struck +Glasgow +rarely +involve +involvement +improvement +ninety +motor +shock +tone +significantly +contact +manufacturing +close +seconds +dress +assumed +well +quietly +grass +nations +provisions +communities +roof +yellow +indicate +distinction +present +statements +comments +allows +late +pollution +fruit +acting +involves +unusual +assume +towns +lucky +fuel +spot +properties +touch +fall +Major +bottle +except +anywhere +net +buying +long-term +soil +sum +stages +decline +missed +reader +extensive +manage +calling +talk +heavily +containing +plate +advertising +revenue +remaining +glad +diet +agricultural +artist +plastic +artists +gently +Bob +alright +location +ring +ice +operate +lies +candidates +Italian +pull +passage +principal +cope +linked +tired +periods +firmly +occasionally +identity +persons +limited +warned +efficient +runs +hundreds +maintenance +divided +unlike +establishment +channel +producing +fight +happening +song +map +expert +formation +comfortable +border +constitution +weapons +emergency +Chinese +waited +continues +arranged +link +Wilson +spokesman +extended +rail +Philip +candidate +believes +funding +promised +positions +mostly +household +remove +performed +cat +sleep +abroad +teams +mountain +program +countryside +stars +victim +studied +relative +criteria +conventional +parish +framework +willing +strike +cheap +ref +sudden +approval +concentration +partners +autumn +maintained +warning +cards +roads +approved +lake +starts +determine +liability +editor +realise +thinks +helping +longer +proposed +voluntary +settled +grant +characters +valuable +situations +deputy +walk +regularly +occasions +trading +rejected +agriculture +premises +dramatic +fill +theme +silver +golden +duties +friendly +arguments +accused +driving +losses +error +reflected +dream +shortly +wealth +working +temporary +federal +stress +painting +request +initially +reflect +lifted +eighty +hello +pub +recovery +loan +electricity +1980s +chest +Margaret +refer +taught +silent +Brown +beach +Indian +eleven +answered +learning +recession +focus +facing +video-taped +height +clubs +item +characteristics +emerged +options +matter +hurt +forgotten +worried +bread +admit +chief +specifically +owners +Lewis +statutory +mirror +agents +writer +deeply +Welsh +foundation +struggle +1978 +parent +dependent +mistake +reputation +Frank +eggs +decade +steel +gain +leads +publication +resistance +offence +incident +Thursday +prefer +stations +denied +examine +lifespan +wages +tasks +gained +acquired +outcome +claims +travel +competitive +marked +panel +resolution +wished +dear +efficiency +demanded +flat +yards +subsequently +gradually +businesses +chancellor +chain +specialist +1977 +dressed +tells +negotiations +relating +supporters +armed +radical +sleep +representation +agencies +theories +outside +shoes +threatened +spending +keen +drove +gardens +acceptable +notion +initiative +stairs +Cambridge +advance +leaves +recognise +worker +essentially +empire +shared +sensitive +uses +clause +attached +Taylor +living +fallen +Belfast +fighting +dear +controlled +sugar +Elizabeth +block +global +delivery +changing +Lee +computers +ages +meat +mass +emotional +brothers +bird +expansion +islands +healthy +Middlesbrough +aside +attend +secret +store +1976 +break +writers +self +rising +travel +bigger +alternative +rapid +instructions +wet +adequate +weak +licence +fixed +soldiers +examined +aimed +owned +average +awareness +centuries +images +drama +notice +Tuesday +handed +furniture +gate +scientists +administrative +sees +pocket +wooden +uncle +remarkable +co-operation +creating +Adam +Luke +newspapers +currency +comprehensive +intelligence +charity +fifth +links +hoping +respond +surprising +extension +solid +survive +growing +apart +restaurant +churches +precisely +pale +skill +close +connection +mass +dealt +brilliant +maximum +losing +depend +200 +experienced +across +introduce +philosophy +convention +gun +films +sons +eh +communications +regime +miss +attended +suffer +copies +councils +round +partnership +inquiry +Sarah +residents +absolute +firm +French +corporation +arrested +reports +minority +arrival +in addition to +listening +taste +sad +gap +plane +scope +experiences +coat +command +consequence +left +fun +Birmingham +tory +golf +electronic +behind +visual +retirement +replace +rise +darkness +fault +directed +complete +1970s +enemy +comment +electric +priority +metres +database +Tim +pure +Spanish +Nigel +rough +core +circle +result +literary +bay +championship +guests +1975 +insisted +mere +bits +successfully +limit +imposed +continuing +Rome +sounds +abuse +categories +languages +tower +Thatcher +anger +accompanied +category +collected +present +need +comparison +supreme +supplied +chemical +fans +greatly +sweet +wedding +teaching +represents +duke +mark +personnel +genuine +adults +mail +politicians +occurs +exciting +written +returning +promotion +longer +preparation +defendant +presumably +DNA +derived +Washington +paintings +fitted +mothers +affair +stupid +cricket +advanced +tank +arise +photographs +point +disappeared +expectations +findings +illness +citizens +mood +faces +tension +Commons +ladies +briefly +stones +mixture +classical +arrangement +extreme +Williams +nineteenth +discover +favourite +shot +causing +yard +begins +socialist +judgment +landscape +fail +feels +consumption +mill +1974 +informed +birthday +widespread +consent +confident +acts +Gloucester +sake +estimated +requirement +catholic +experts +Israel +numerous +throat +permission +ignored +guidance +moments +brings +costs +module +opposite +respectively +altogether +input +presentation +everywhere +distinct +statistics +repeated +tough +earnings +saved +finger +branches +fishing +components +truly +drink +turns +luck +boss +exists +champion +MP +answers +tools +cycle +recommended +intervention +mile +Scott +whenever +AIDS +promote +helpful +prospect +definitely +Johnson +organised +controls +Ben +express +Iraq +nurse +frame +perform +cottage +wave +adding +proud +by +winner +solicitor +neighbours +pilot +calls +mad +alliance +given +straight +survival +winning +votes +primarily +attacks +compensation +Sam +destroyed +camp +hat +territory +symptoms +prior to +ownership +wage +concluded +discussions +developing +cream +achievement +drawing +entrance +basically +poverty +disabled +extend +fast +suggestion +consistent +shareholders +degrees +mention +anxious +fewer +delivered +dark +transferred +employee +throw +assumption +inevitably +nervous +profession +awful +cool +hang +threw +vehicles +stable +realized +suit +hills +prize +drop +constitutional +perspective +Neil +satisfied +bid +aunt +festival +constantly +conscious +developing +connected +concerning +savings +reasonably +concentrate +pace +novel +operating +breach +purchase +crossed +Asia +chances +depth +calls +strategic +thrown +bills +Jean +Ken +reply +ha +Ruth +treat +green +sheep +dominant +phrase +push +eating +now that +conducted +employer +ears +contents +touched +prepare +sounded +Moscow +theoretical +setting +soul +wore +study +1960s +outstanding +benefit +vary +jacket +except +holy +processing +sand +clinical +prisoners +dispute +shadow +minimum +organizations +plaintiff +snow +cried +fit +driven +Joseph +port +recognized +servants +limits +pink +et al +sound +hearing +measured +dance +eighteen +sorts +Patrick +trained +stomach +slight +fought +points +storage +breaking +impressive +honest +provided +dismissed +glanced +related +cast +crew +defeat +hold +gift +enthusiasm +princess +press +spending +advantages +reaching +articles +resulted +files +hung +cuts +residential +extraordinary +visible +shouted +reducing +experiments +tables +finish +tendency +conduct +objective +report +ring +hospitals +mechanism +seventy +exception +poetry +inspector +1973 +covering +stepped +accurate +percent +victims +approached +distant +alongside +airport +furthermore +considerably +pressed +missing +origin +salt +personality +fight +Canada +arrive +fly +Greek +receiving +still +rocks +fees +fee +complicated +ends +musical +stands +moon +chamber +puts +turnover +attacked +ultimately +routine +observation +precise +plain +gentle +watch +staring +since +leisure +economics +device +broken +mortgage +live +leaves +confusion +cut +finds +instruments +secondly +Bush +certificate +ear +remote +satisfaction +responsibilities +ratio +coach +fears +sentences +holder +shopping +possession +selected +Bristol +sets +smoke +rugby +songs +clock +summary +implementation +protein +housing +escape +wing +fixed +helps +poll +Arthur +scored +chose +column +holidays +contributions +architecture +Nick +approximately +disaster +minds +Keith +marks +trust +neither +monetary +mountains +White +discovery +collect +spoken +steam +smooth +silly +childhood +teach +Kong +unity +staying +climate +percentage +villages +attracted +Americans +designs +secure +cutting +iv +Hong +last +taxes +tennis +peak +relation +readily +evaluation +frequency +Andy +shirt +cake +nights +bishop +test +pretty +Charlie +deliberately +round +determination +applies +automatically +standing +rent +psychological +violent +medicine +boards +protest +Anna +unemployed +final +being +Rose +reforms +inevitable +junior +signal +building +till +welcome +fat +roles +headquarters +sensible +visits +manufacturers +restrictions +samples +Pacific +improved +Berlin +grateful +strategies +score +tended +expense +loans +addressed +mode +structural +dozen +pride +newly +founded +variation +aged +rely +investors +infection +dominated +combined +survived +Helen +string +Lucy +experiment +fourteen +undertaken +committees +buyer +agreements +participation +welcome +match +complaints +ships +critics +guitar +camera +laboratory +waves +landlord +rang +hate +demonstrated +bomb +engaged +knife +so-called +modules +pleasant +headed +surgery +universities +millions +concepts +proof +marry +Maggie +operating +pressures +lives +sixteen +repeat +host +Dave +pitch +attempted +assess +penalty +tail +boxes +holes +deep +contribute +passing +reveal +cleared +thereby +acceptance +1972 +anxiety +above +Newcastle +formula +personally +Howard +mission +deaf +relatives +imagination +apple +dirty +rid +abandoned +appreciate +continuous +describes +suffering +circuit +stronger +responses +excitement +approaches +supply +plan +Zealand +tested +disk +holds +replacement +instrument +universe +memories +overseas +expertise +causes +solicitors +comfort +sergeant +trend +treasury +entitled +sounds +acquisition +opening +tickets +bath +delighted +sending +increasing +confirm +loose +state +targets +occasional +paragraph +writes +evident +Kent +desperate +handle +fellow +blind +occupied +overcome +dust +burden +psychology +relate +drivers +surprisingly +1970 +Matthew +cheese +consciousness +considering +universal +Gordon +aha +square +succeeded +knees +1971 +boots +smell +closer +mummy +slipped +component +regulation +Roger +attempt +locked +keeps +wheel +classic +MPs +cattle +consists +touch +illustrated +platform +shift +draft +purely +load +influenced +passengers +recommendations +preparing +solutions +Alexander +injuries +tenant +commonly +Victoria +leather +gathered +zone +sufficiently +like +Laura +squad +recall +steady +retain +checked +existed +attract +conservation +flesh +pack +publicity +rose +mean +variations +split +sixth +edition +concerns +tied +summit +engineers +Terry +listed +judges +researchers +equivalent +assist +upstairs +hers +Francis +located +nurses +fear +mark +suggesting +whispered +serving +intellectual +influence +stream +generated +consequently +San +authors +wondering +judgement +experience +Victorian +egg +supplies +level +produces +councillor +roots +taxation +bathroom +ultimate +awarded +stick +glasses +raising +qualities +layer +lost +creative +medieval +risks +assumptions +displayed +dreams +Germans +accounting +curve +drawing +backed +adopt +colleges +guard +evolution +sign +aims +sharply +constructed +advised +softly +settle +decades +completion +linguistic +ignore +convinced +Colin +judicial +photograph +sophisticated +Alice +asleep +paused +amounts +poem +recording +carbon +Durham +possibilities +good +explains +equation +NHS +vulnerable +raw +net +deaths +babies +illegal +outer +topic +medium +till +promise +tends +hopes +angle +interviews +ban +feed +potentially +machinery +tongue +coalition +travelling +define +consultation +reception +pulling +Nicholas +integration +revolutionary +quoted +compare +surrounded +bitter +attempting +guess +improvements +climbed +cathedral +heaven +wanting +painted +Australian +changing +grammar +jumped +Ulster +Gulf +native +imperial +persuade +voices +conclusions +laughing +lift +1968 +Gary +boundaries +favour +guy +studying +Jimmy +beliefs +undoubtedly +wings +retained +joy +bone +informal +demonstrate +Douglas +regard +clear +calculated +flexible +meals +announcement +lawyers +ruled +account +sheets +tunnel +exercise +bars +carpet +quantity +catalogue +reminded +shrugged +notably +Anthony +schedule +petrol +investigate +hotels +buried +once more +journal +nowhere +considerations +concentrated +collective +destruction +frequent +versions +offences +agenda +clever +experimental +plays +Kate +listened +La +texts +plates +deficit +transition +Norman +spiritual +intense +indication +flew +pushing +rational +hanging +entitled +excluded +knocked +professionals +tight +composition +indicates +conservatives +Kevin +interaction +ceiling +guidelines +cold +roughly +Governor +qualifications +ethnic +me +argues +Dublin +inches +opera +pupil +cheaper +generous +prominent +inadequate +accordingly +welcomed +instruction +logical +passion +drawings +exposure +departure +blame +racing +mixed +historic +guest +Mrs. +pipe +modest +Dutch +lessons +hero +Lloyd +sectors +Diana +barely +logic +Essex +acute +harm +representing +discourse +voted +electrical +hearing +consumers +jury +Grant +weekly +acted +delay +valid +wherever +representative +transaction +bowl +increases +contributed +Christopher +record +leaned +lesson +lit +admission +stores +awards +automatic +timber +trousers +vote +habit +Oliver +arrange +red +matches +punishment +bones +cross +deny +rubbish +hide +mortality +complex +pc +earl +explore +urged +occupation +storm +darling +keys +customs +profile +gross +depression +classroom +glance +mystery +mutual +reliable +wholly +entering +bare +liable +facility +stressed +stuck +realize +engineer +smiling +confined +province +registration +males +laughter +humour +resource +multiple +Albert +ruling +silk +waters +Rachel +paint +cotton +Atlantic +identification +claiming +sole +coverage +arising +Owen +honour +poet +prospects +travelled +divisions +posts +avoided +in case +charter +managing +pregnant +obligation +win +adds +formally +flying +Latin +nearby +Egypt +exact +directions +curious +bother +participants +lawyer +resignation +bearing +sets +pointing +tool +damages +speakers +fate +daddy +devices +phenomenon +strain +substance +bags +wire +Wood +underlying +responded +enjoying +visitor +joining +uncertainty +but +drop +submitted +flower +Ford +California +perception +identical +farming +letting +audit +satisfactory +Billy +ticket +lists +preference +Great +thirteen +Van +secret +pop +album +federation +learnt +deliver +Westminster +chemicals +farmer +variables +male +assault +marginal +leave +namely +fed +distinctive +kingdom +assessed +refuse +electoral +urgent +allowance +observations +libraries +Lawrence +reflects +force +sympathy +running +falls +publishing +recovered +stability +canal +funeral +singing +titles +beds +sessions +restricted +Sheffield +Nottingham +expecting +clothing +drinks +disposal +failing +joke +focus +succeed +Maria +typically +official +conversion +presidential +generations +mayor +sharing +Clare +worth +transactions +era +policeman +Fred +gaze +controversial +count +proceed +Young +folk +fabric +oral +horror +Kelly +everyday +emperor +viewed +sing +belt +fortune +demand +doubt +crash +encouraging +interpreted +Louis +organic +maintaining +removal +female +routes +continued +trials +enables +print +laugh +bent +expected +connections +magistrates +errors +statistical +resolved +desirable +recognize +Stuart +thoroughly +injured +van +blocks +prosecution +register +trends +preferred +reckon +innocent +ideology +belong +improved +past +corridor +exclusive +tale +pairs +prayer +collapse +lease +talent +gains +separated +marked +experienced +persuaded +sighed +butter +suggestions +Russell +unexpected +foods +picking +banking +sciences +superb +contacts +operated +alarm +go +Poland +gene +daughters +sheer +guardian +count +cloud +disappointed +Bernard +format +scenes +frightened +hardware +traditionally +gastric +genes +effectiveness +full-time +intend +concentrations +defend +strict +fighting +creatures +closer +Swindon +capitalist +Walker +addition +chocolate +emerge +Hugh +hidden +likes +Susan +Stewart +reactions +lands +establishing +swept +anniversary +permitted +export +1967 +justify +tissue +Davies +bet +specified +romantic +garage +conviction +declined +resigned +Clarke +advise +scientist +root +asset +warmth +bulk +bands +knee +minimum +humans +references +any +associations +muscles +withdrawal +registered +distributed +regarding +exposed +declaration +graphics +reluctant +actor +switched +sisters +winners +eighteenth +chemistry +rest +justified +stop +converted +boundary +suspect +magnificent +stretched +convenient +friendship +established +recover +destroy +Jackson +mess +correspondent +navy +dollar +craft +reflection +chicken +plans +tin +Miller +curtains +gesture +tourist +diary +protected +ocean +discussing +practitioners +bloody +entertainment +nearest +mechanisms +closed +expenses +uncertain +artificial +democrats +damaged +composed +heating +diplomatic +drinking +discrimination +rows +bench +councillors +acquire +installed +guns +killing +Microsoft +blow +salary +Baker +tip +1950s +physically +estates +tremendous +marine +ease +institutional +mechanical +retail +resist +mixed +literally +chapel +distinguish +wildlife +Rivers +Iran +tories +doubts +formerly +priorities +reserves +publications +commented +gender +passenger +Sussex +strictly +boats +causes +pen +chapters +cheque +required +testing +carriage +weapon +generate +Clinton +asks +earn +supporting +mentally +judge +messages +females +biological +applying +implies +known +Emily +rolled +tube +functional +accidents +flexibility +chairs +Phil +styles +cap +straightforward +moves +wise +fired +organized +inspection +Derek +mathematics +heritage +superior +1969 +specially +finance +cloth +sociology +desperately +fiction +equity +satisfy +Lords +shell +Wright +lad +whereby +forests +suit +pursue +digital +increases +tenants +refers +voters +piano +productivity +part-time +lightly +assistant +Commander +address +situated +restoration +outlined +imports +comment +stolen +Harris +clerk +cinema +Ann +covers +capitalism +spectacular +shapes +controversy +Marx +gates +escaped +Robin +continuing +trains +ensuring +colonel +confused +grants +remarks +bonds +wives +computing +constraints +solve +aggressive +availability +unfair +sadly +invasion +tracks +compete +closure +spare +painful +earned +venture +topics +wonder +equivalent +grade +Korea +pot +emotions +washed +escape +abstract +Eric +murmured +stake +lift +states +breeding +securities +asian +mud +Joan +estimates +cheek +stored +correctly +refugees +Moore +obligations +spirits +unhappy +Ross +networks +beaten +snapped +initiatives +understanding +alter +shame +pensions +oxygen +therapy +associated +courage +discretion +dates +deposits +hopefully +exports +legislative +Eliot +ward +monthly +deciding +describing +assuming +opposed +Alex +searching +intelligent +impose +explicit +jurisdiction +designer +tie +fellow +quantities +fleet +Barry +seller +RAF +borough +stand +flats +virtue +constituency +complained +coloured +midnight +taxi +engines +railways +display +just +ridiculous +Caroline +debts +comparable +amazing +acknowledged +appeal +wars +successive +refusal +incorporated +creature +secured +economies +isolation +Leicester +succession +signals +working-class +physics +feared +concert +tonnes +realistic +hungry +launch +Evans +resort +burst +sort +back +Walter +gear +Shakespeare +surveys +volunteers +stick +separation +la +demonstration +fails +conception +decent +discount +unnecessary +prevented +flying +worn +dictionary +twentieth +fat +random +retired +local +origins +packed +achieving +heading +forever +influential +masters +channels +harbour +producers +duration +Thames +cable +1945 +desert +terrace +assured +allocation +check +diseases +merchant +constable +Vietnam +Dean +recalled +lifetime +chips +Ray +genetic +complaint +near +visiting +explaining +order +marvellous +Malcolm +Morgan +restored +earliest +enabled +release +Cardiff +assurance +bottles +brick +essence +autonomy +giant +requiring +hunting +consensus +differ +vegetables +junction +workshop +measure +purchaser +secure +attendance +necessity +bottom +demanding +skilled +shaking +subtle +select +attack +questioned +sooner +producer +planet +elegant +amendment +hopes +carries +recommend +lesser +farms +parallel +limitations +locally +Marie +tragedy +instance +cousin +collections +backwards +grain +resulting +fraud +swung +landed +quarters +liberation +seventeen +referring +interior +bike +suspended +officially +journalists +nasty +movie +suppliers +dealer +shows +soldier +intensive +kit +witness +delight +symbol +forum +casual +tropical +shorter +Allen +crimes +printed +miners +feeding +relax +pass +manufacturer +chip +crazy +forming +kissed +swimming +happily +copper +arguing +shots +landing +nursery +entries +preliminary +besides +arises +partial +households +damp +wool +1964 +servant +Pakistan +attending +Guy +plot +muscle +beings +inch +simultaneously +concrete +Roy +roll +bell +neighbour +reign +analysed +tide +expand +alleged +guilt +rank +introducing +transfer +uses +ceremony +Morris +separately +opinions +enquiry +grinned +lover +slept +choices +assistant +severely +finest +poured +vertical +Easter +upset +hey +allegations +IRA +justification +detective +programs +throwing +strike +ate +appendix +Jenny +districts +commonwealth +dealers +delicate +forms +advisers +lonely +dull +mouse +Pat +occupational +pity +behave +complexity +youngsters +riding +weakness +excessive +Clark +progressive +captured +stance +undertake +exceptional +faster +Iraqi +remind +counter +Greece +triumph +remarked +continental +striking +integrated +pit +encountered +implemented +sizes +directive +participate +safely +lowest +lighting +villa +okay +downstairs +portrait +alternatively +edges +focused +bye +residence +panic +label +aims +magazines +neat +combined +transformation +theft +lecture +incidence +scores +radiation +perceived +spread +firstly +interface +doctrine +shouting +affecting +ours +excuse +accepting +risen +Lancashire +approach +deposit +pond +substantially +innovation +diagnosis +gifts +allocated +regard +remainder +speculation +approaching +dialogue +estimate +wash +supervision +dying +exclusively +happiness +politically +timing +chronic +Geoffrey +peasants +tightly +characteristic +accuracy +compulsory +wrapped +interim +objective +Benjamin +walking +infant +Bruce +judged +splendid +ride +divorce +magic +Cleveland +bond +review +short-term +ambulance +brave +investigations +systematic +Green +seized +cry +laugh +advanced +obliged +opens +eaten +relevance +1930s +careers +Liz +withdrawn +Barbara +no +payable +handsome +fun +Ms +instances +governors +horrible +measurement +employ +primitive +steadily +switch +fascinating +Brazil +ideological +pile +mounted +metropolitan +alternatives +dollars +north-east +explosion +starting +glory +scarcely +Harriet +surrounding +coup +domain +fence +threatening +dragged +breast +habits +mine +hierarchy +grip +socialism +enquiries +particles +Sweden +choosing +colleague +monitoring +Midlands +restore +printer +imagined +doorway +prisoner +juice +classification +estimated +equilibrium +solely +with regard to +serves +peaceful +observer +explanations +circles +rescue +maps +hated +observe +Hughes +premier +mate +hypothesis +1966 +ride +companion +liver +factories +buyers +reward +controlling +satellite +loyalty +operational +pardon +improving +jump +potatoes +intervals +technological +near +fortunately +hostile +advisory +cook +precious +opponents +peasant +insist +geography +button +consistently +cultures +seeds +monopoly +accessible +tournament +moves +excited +determined +owed +pockets +belonged +Hollywood +dining +switch +traditions +compromise +intensity +chaos +obtaining +Mexico +King +combine +altered +nonsense +clouds +themes +suspicion +ranks +disorder +stocks +Kuwait +1965 +2000 +consultant +collapsed +purchased +impressed +half +Catherine +provincial +sterling +performances +instantly +Bell +constitute +arrest +dose +exercises +issue +competitors +spectrum +dangers +allies +travellers +plc +kid +disc +Donald +nowadays +Surrey +cheeks +endless +isolated +dimension +twin +bedrooms +clean +columns +privilege +post-war +volumes +broadcasting +commerce +historians +train +geographical +oak +actors +step +like +dynamic +freely +checking +equipped +inspired +density +1994 +forthcoming +HIV +boring +handled +poems +recording +unfortunate +banned +Karen +own +suspected +boom +tribunal +kicked +possessed +Jonathan +broadly +publicly +attributed +definite +challenged +extending +cooking +pause +strip +predicted +super +barrier +pregnancy +loud +menu +preserved +Avenue +restaurants +acres +prompted +senses +essay +lip +recruitment +defendants +presents +guarantee +invest +cats +maximum +notable +upwards +arose +cry +fierce +detected +indirect +German +witnesses +patch +sensitivity +Le +mistakes +receiver +crops +chin +wheels +rice +Dec +forgot +illustrate +reveals +Freud +limit +chap +Campbell +races +awkward +Turkey +implied +climb +widow +varied +slid +stopping +rope +steep +neutral +Oxfordshire +finish +debut +seed +challenge +promoted +delegation +hitherto +artistic +muttered +adoption +architect +dear +Kenneth +portfolio +continent +transformed +couples +probability +content +Robinson +struggling +mild +counties +wish +mention +fitness +tackle +dish +statute +invariably +Charlotte +prey +view +consultants +gather +arriving +corners +delegates +Holland +archbishop +Sue +withdraw +replacing +Milton +meaning +mature +differently +chart +technologies +woods +possess +cab +grace +toilet +grabbed +prevention +equality +wishes +bases +operator +regardless +harsh +colonial +ambitious +exploration +lords +investigated +collecting +Switzerland +shadows +Corbett +evil +Johnny +dramatically +Marshall +indicating +orchestra +lock +inhabitants +defeated +disappointment +magnetic +washing +fibre +correspondence +verbal +legitimate +requested +emotion +odds +workforce +vessels +brass +pursued +ph +balls +adviser +faint +handling +appointments +grandfather +motivation +sympathetic +publishers +peoples +socially +investments +rhythm +variable +Chelsea +memorial +well-known +empirical +roses +ceased +fluid +descriptions +incidents +DC +dismissal +appreciated +communicate +rushed +bronze +wisdom +Daniel +supper +adventure +tribute +seeks +promise +head +ye +1960 +crop +beef +suited +exercised +respects +terror +circulation +identifying +achievements +fool +intentions +proportions +lads +directory +Brighton +inn +promoting +flag +separate +Roberts +Ward +Dennis +clay +Cook +Norway +attraction +ends +disability +championships +vague +virus +shift +ranging +competence +examining +inform +spaces +goodness +gang +favourite +preserve +remembering +naval +molecules +hearts +trapped +actively +leaf +Brussels +distress +resolve +custody +packages +drinking +operators +myth +gain +voting +Mick +returns +tourists +encouragement +lacking +seldom +processor +sums +integrity +acknowledge +shortage +depressed +rightly +Louise +remarkably +repair +shoot +electronics +wishing +Kinnock +imprisonment +kings +waved +shared +shocked +uniform +added +reject +implement +pays +hesitated +seventh +magic +mid +populations +worthwhile +filling +crystal +fraction +qualified +Newton +Sally +server +Nato +specimens +kiss +reflecting +shower +missing +roll +sword +varieties +clinic +imply +ie +rivals +Julia +breakdown +Anderson +scales +fan +operates +blank +whoever +scandal +oldest +smart +favourable +filter +interviewed +absent +mining +gentlemen +enemies +champions +Duncan +exclusion +boot +locations +Hamilton +transmission +custom +tanks +tries +Gloucestershire +publisher +beating +evidently +Netherlands +Polish +lively +exceptions +Emma +appeals +Israeli +mobility +reviewed +buses +conclude +mix +shore +commissioner +absorbed +Norwich +dawn +developed +guards +incomes +parking +vendor +wishes +republics +loads +barriers +translation +evenings +Hungary +lectures +stimulus +conflicts +remains +margin +question +bothered +neighbourhood +tourism +meanings +FA +desktop +reportedly +risk +zero +demonstrations +dividend +opponent +wake +stiff +rejection +flavour +relates +borrow +emissions +representative +Midland +thereafter +enthusiastic +observers +cited +quid +fortnight +dreadful +guarantee +reduced +rigid +killer +ending +trick +successor +execution +influences +temperatures +mines +drank +coastal +greeted +nightmare +peculiar +corruption +tray +speaks +cupboard +creates +Jordan +Aberdeen +harder +burned +appearing +Swiss +rabbit +environments +comedy +referendum +bureau +avoiding +just about +matrix +honestly +profound +journalist +extended +Julie +tapes +suspension +delayed +eager +comply +selected +skirt +matched +feminist +Davis +Canadian +closing +acts +grief +relaxed +insight +deck +sensation +placing +sequences +temple +parks +tactics +verdict +adapted +enhance +corresponding +strings +accurately +running +pray +accent +envelope +interference +grandmother +examinations +phone +planned +shelf +deemed +waist +waste +onwards +applicable +futures +sauce +immense +purchase +breathing +allied +Norfolk +contest +expects +supports +km +blacks +decision-making +coins +genuinely +accounted +expressing +assessing +dance +scheduled +adjustment +charge +winds +meets +practically +merger +comparative +permit +celebrate +vessel +belonging +affection +outline +albeit +Lily +leaning +lounge +raises +Cheltenham +workshops +refusing +shallow +dishes +monitor +propose +blamed +dioxide +kind +broader +handling +bastard +uncomfortable +affects +proposition +representations +conservation +ya +makers +Yugoslavia +Fox +citizen +forcing +productive +woke +bored +beneficial +slip +campaigns +handful +aged +collar +curtain +diversity +hint +Thompson +disappear +charming +bonus +secrets +interrupted +specialists +accommodate +frustration +recommendation +meantime +coffin +daily +condemned +minimal +mobile +academy +testing +independently +appealed +museums +cruel +faces +murdered +on board +Turkish +aim +Will +territories +pressing +Churchill +commit +verse +research +orange +interval +threats +passive +suspicious +forgive +liberty +ghost +rear +believing +correlation +measurements +1963 +investigating +shade +layers +bias +overwhelming +certainty +Sunderland +cow +commissioned +trusts +maturity +resulting +fatal +surrounding +crying +planted +symbolic +isle +historian +enabling +removing +slope +excuse +angel +nearby +rats +straw +1962 +surfaces +gods +foundations +honours +Belgium +disputes +insects +inspiration +draw +presenting +registered +pavement +telephone +reserve +keeper +dimensions +predict +neighbouring +validity +breeze +ugly +expanded +lasted +irrelevant +complain +shelter +patient +driving +wealthy +upset +hostility +profitable +rod +fled +compact +lamp +shifted +supplier +crossing +phenomena +IT +measuring +horizon +rival +making +clergy +marble +pensioners +fragments +loyal +Alison +Stanley +conscience +sixties +Hill +saving +tune +moderate +1961 +soup +paths +struggled +popularity +score +singer +distinguished +climbing +kick +Betty +characteristic +interior +episode +oven +basket +noble +forwards +consisted +crowds +positively +pole +burning +pet +insufficient +evil +mysterious +jet +eligible +behalf +passes +nails +collaboration +lorry +nest +varying +enforcement +Spencer +Denmark +make-up +molecular +managerial +raid +ambition +middle-class +brand +migration +embassy +neatly +looks +worship +Olympic +devised +exclude +organ +favoured +linear +Samuel +cared +manor +detect +interpret +Kennedy +substances +crude +fantasy +counselling +abilities +treating +blew +embarrassment +executed +implication +Ron +printed +prospective +importantly +Preston +continually +Barnes +executives +catching +forehead +Ali +diverse +parental +elaborate +furious +definitions +appreciation +fiscal +Kim +commitments +sculpture +runs +striker +beans +brush +soccer +spell +reductions +contrary +soap +dated +stretch +publish +russians +pig +stroke +ladder +Greater +burning +expressions +useless +nerve +pence +Gabriel +rumours +relied +Edwards +semantic +inherent +embarrassed +1948 +specification +despair +yep +name +serum +Maxwell +Dick +apartment +Vienna +deliberate +stranger +philosophical +criterion +trap +pubs +utterly +link +frowned +awake +bureaucracy +nonetheless +sunshine +bloke +partially +remedy +battery +variable +within +forth +barn +ties +settlements +installation +crashed +negotiate +Somerset +nursing +dignity +promising +minus +criticised +sacred +analyse +senate +incentive +unpleasant +varied +selective +qualified +Devon +powder +clauses +expectation +tender +inclined +funded +alleged +hidden +ridge +exhibitions +lengths +Joyce +posed +explicitly +symbols +exploitation +receives +1950 +intermediate +Isabel +blocked +trophy +launch +spotted +manufacture +diesel +masses +protective +paint +budgets +Lisa +grows +fortunate +deserve +lap +concerns +varies +compliance +defensive +damage +objections +qualify +featured +suite +salmon +reach +requests +objection +devoted +thesis +repeatedly +blow +palm +Austria +Rover +parked +Carter +Guinness +temporarily +Land +south-east +chains +worthy +ozone +pursuit +valued +divine +react +deals +head +phoned +carrier +jeans +feedback +dancing +tales +rally +grant +performing +rush +handicap +consisting +counted +qualification +guaranteed +negligence +continuity +lend +offers +educated +stuck +surplus +swallowed +eagle +printing +land +Willie +novels +driving +dependence +1st +eighth +Craig +organise +Cornwall +orange +diameter +1939 +toward +auction +eating +Max +invisible +determining +construct +faculty +offenders +occurring +Pete +charm +Don +suffering +contempt +Wimbledon +reinforced +specify +misery +dropping +breasts +overall +Sara +jewellery +bacteria +sin +comparisons +privatisation +owe +squadron +grave +codes +circular +misleading +centred +sunlight +lowered +invested +mathematical +proteins +sanctions +aggression +caution +loch +reply +direct +subjected +inappropriate +diagram +terribly +St +human +liquid +solar +angles +sorted +persistent +poles +laying +inherited +phrases +doubtful +calcium +shake +ingredients +Sophie +admits +black +BR +monster +flames +allowances +sustain +needle +telecommunications +sphere +revenues +guessed +bowel +doubled +prints +rangers +accountants +screaming +legend +petition +predominantly +manual +lies +premium +photo +surroundings +spots +gravel +19th +architectural +bold +Maastricht +inheritance +Harvey +knock +blues +beyond +Day +emergence +beautifully +deeper +intact +cooperation +convince +incredible +sound +devoted +conduct +united +celebration +abruptly +considers +flights +explored +loves +blue +Derby +restriction +prior +submit +gaining +Santa +morality +tragic +musicians +invite +Ipswich +selling +script +coupled +tap +remark +consist +respectable +pint +optimistic +humanity +layout +openly +breed +policemen +Scots +invented +linking +convincing +Harold +guide +vocabulary +Rob +unacceptable +competent +Carrie +spatial +ignoring +applicant +swiftly +easier +painter +decisive +traders +pretend +bargaining +depended +modes +preventing +rage +respective +elite +permanently +seemingly +bunch +carers +fathers +engagement +liquid +Canterbury +binding +fires +sentenced +rebels +founder +ballet +erosion +Gould +syndrome +relieved +nursing +harmony +Coventry +protested +hut +sits +stops +Lamont +bore +instructed +fertility +toxic +testament +1957 +sickness +stretch +Bath +lemon +practise +mix +faster +integral +select +redundant +handle +throne +conceived +polytechnic +nerves +belongs +privately +burn +gravity +labelled +Alfred +bishops +basin +rings +holders +swing +flood +Christie +evolved +sovereignty +then +applicants +cows +lion +Virginia +trail +smoking +trading +Murray +boxing +amateur +probable +scrutiny +tempted +borders +pan +fix +hydrogen +accountability +consulted +echo +sponsorship +fame +El +lakes +protests +patience +documentation +Geoff +backing +search +Mozart +silently +passing +seasons +recipe +fetch +auditors +territorial +specified +abandon +bombs +Los +mineral +horizontal +lined +Robyn +booked +du +cleaning +bear +old-fashioned +inland +youngest +envisaged +floors +thrust +likewise +strengthen +penny +wake +Bradford +overseas +consult +cognitive +Ralph +dock +reaches +disturbed +communists +slim +synthesis +contexts +revival +Reading +regulatory +hurried +defender +dry +miserable +walks +debates +dancing +isolated +venue +Hampshire +resident +rounds +deals +packet +likelihood +remaining +induced +guys +temper +comparatively +calculations +protecting +holdings +corn +1947 +Yeltsin +fusion +Marxist +conferences +creditors +questionnaire +gothic +scared +willingness +civilian +shelves +reporting +precision +divide +Phillips +overnight +Intel +Linda +deputies +Indians +Trevor +Juliet +Watson +conventions +modified +instant +praise +Des +coin +blown +hiding +galleries +1940 +Constance +outlook +incurred +adverse +subsidiary +tiles +seventeenth +Korean +emphasised +Eddie +bile +1959 +fancy +accounting +leaflet +headmaster +crack +heels +truck +engage +reporter +plays +Steven +calm +initiated +brigade +Dorothy +unconscious +convicted +illustration +trustees +sustained +alike +End +ideally +entity +tons +sang +telegraph +negotiation +opposite +smell +aesthetic +wiped +concentrating +anonymous +trace +usage +orthodox +fulfil +polite +girlfriend +lovers +translated +static +intent +cancelled +inside +unaware +presidency +corps +assigned +appearances +exploit +margins +worldwide +cups +solved +panels +halt +EEC +Suffolk +developers +fantastic +Lancaster +seminar +fashionable +criticisms +Cooper +motorway +zones +foolish +intake +advances +receipt +rule +regiment +trades +manual +backs +duck +causal +convey +Tommy +wee +cleaning +fond +compatible +Southampton +inclusion +Herbert +finding +lengthy +two-thirds +tent +shed +implicit +cameras +dare +abolition +Romania +pigs +lace +dedicated +cuts +perceptions +ft +counts +earning +kiss +confirmation +dual +confronted +twenty-five +mistress +assignment +propaganda +toys +Arsenal +Eleanor +critic +curiosity +republican +pipes +reduces +shooting +cheerful +reporting +plea +distinguished +subjective +pie +priests +returns +tel +labels +width +relaxation +advertisement +white +smoke +pencil +legally +following +lacked +surviving +disadvantage +ruling +forward +sleeping +owl +adequately +reproduction +rewards +architects +rear +Shelley +exotic +ambassador +1914 +camps +displays +passages +gazed +timetable +salad +purple +cautious +visiting +Turner +aggregate +ignorance +anticipated +Parker +redundancy +array +penalties +renaissance +theology +try +warn +process +ethical +major +proving +plain +protestant +grid +tenth +takeover +canvas +Ted +skull +highlighted +jokes +beat +pools +twins +borne +criticized +chemical +omitted +revision +sincerely +prizes +salvation +teenage +responding +indicators +repairs +amnesty +comparing +large-scale +yield +Claire +photography +disastrous +thumb +dying +jointly +kilometres +scholars +ace +lump +delicious +confidential +clash +market +underground +Blanche +armed +destination +witnessed +parameters +costly +restraint +bit +1958 +shaped +rode +tips +prosperity +diamond +fury +instinct +reserved +valuation +contacted +subsidies +Hunt +collector +Darwin +sponsored +compound +strengths +sank +defences +lifestyle +prejudice +announce +apparatus +dot +shoe +blanket +wound +Christine +hunger +cabin +photographer +stay +preservation +calendar +assessments +colony +Katherine +thorough +medal +trips +washing +eliminate +breathe +actress +provinces +helicopter +mist +clue +dominance +relaxed +analysts +searched +grin +Czechoslovakia +hitting +inability +portion +restrict +Gray +conspiracy +Nicholson +mercy +log +autonomous +intends +solidarity +jail +genius +1920s +pilots +incorporate +atomic +blade +frozen +1956 +colourful +discharge +injured +mask +provided that +Trent +ease +draws +retire +supposed +ml +angrily +sigh +stamp +adjust +ferry +concessions +majesty +Gilbert +pylori +uniform +adjusted +ashamed +admired +alpha +referee +1944 +Lebanon +respondents +Collins +rested +reconstruction +flown +individually +jaw +submission +efficiently +bitterly +glorious +pour +illustrates +Angeles +amid +convert +wicked +provoked +Chapman +elbow +videos +coherent +annually +le +rising +disciplines +cliff +boyfriend +novel +controls +sweat +depths +Claudia +cave +balanced +strikes +stretching +pains +Close +Tokyo +Portugal +racism +priced +delightful +evaluate +arbitrary +Chicago +Richards +signature +reversed +heroes +clarity +hit +screamed +adjacent +lid +psychiatric +comprising +honey +temptation +beam +immigration +recordings +worrying +weird +practitioner +unchanged +calculation +tutor +politician +rolling +Athens +expedition +electorate +evolutionary +scattered +abolished +researcher +ports +Chester +dilemma +Carl +loaded +IMF +flung +intimate +fever +parallel +tight +miracle +including +lawn +biology +Lothian +failures +breaks +Angela +shy +appraisal +sporting +wines +cleaned +disciplinary +occurrence +smile +formidable +lexical +graduates +fined +cooking +privacy +needles +Reagan +Black +Ed +sink +march +equations +grim +narrative +HP +charts +polls +Paula +express +OK +limbs +decorated +high +addressing +proceeds +pact +Madame +Merseyside +revenge +vice-president +far from +proceeded +airline +minerals +killing +accused +double +gradual +descent +mount +homeless +courtesy +enhanced +supermarket +Blake +Cheshire +interfere +organisers +managing +monitoring +coming +rat +supporting +Marcus +trace +approve +delays +pm +Reynolds +please +yo +programming +training +renewed +Hull +invention +writings +back +excess +planes +legacy +challenges +gaps +dug +Jason +interpretations +smallest +pulse +analyses +Ashley +rubber +retired +specimen +outdoor +shooting +chosen +embarrassing +wrist +atoms +Hereford +smoking +incidentally +preferred +renewal +Japanese +vanished +hook +loudly +bride +Annie +interactions +bizarre +gospel +realm +mainland +knit +appalling +exchanges +surgeon +crews +orientation +twisted +occupy +flame +hatred +exceed +Maurice +laboratories +reviews +Bosnia +agreed +Butler +utility +conversations +imaginative +pursuing +flour +accepted +wartime +governing +Reid +object +cutting +indirectly +governed +palestinian +vocational +von +modification +slopes +allegedly +parade +free +aluminium +Al +movies +biscuits +motive +register +merchants +hip +print +rabbits +remedies +stress +trainer +welcome +wound +Geneva +configuration +boost +puzzled +encounter +axis +no matter how +Clive +worldwide +Arnold +Allan +lamb +laser +vegetation +reluctance +jazz +databases +strengthened +protocol +enjoyment +organisational +knitting +census +calculate +handicapped +mucosa +theirs +1951 +advertisements +eldest +Carol +1936 +eventual +husbands +fur +followers +wasted +pump +lifting +practised +yacht +toes +stimulate +speeches +1953 +traced +ensured +arrow +journals +weekends +spontaneous +appoint +1949 +binding +superintendent +vivid +corporations +organisms +celebrated +mice +motives +torn +tie +dies +1954 +waiting +classified +organs +lack +worlds +nineteenth-century +faithful +shield +withdrew +reckoned +north-west +rolling +missiles +noisy +hire +organising +quote +sofa +reminder +Venice +ministerial +A +daylight +injection +graph +exchanged +prayers +boost +preparations +borrowing +innovative +strongest +audiences +disclosure +confrontation +constitutes +burnt +liaison +armies +strangely +wounds +Hewlett-Packard +controlled +Newman +cease +incentives +extends +Mitchell +echoed +facilitate +resentment +shout +cage +gloves +1990s +exploring +saving +Leonard +crossing +choir +Gibson +exit +Sydney +assumes +woodland +fog +underneath +promises +Ellen +nationalism +Kenya +commentators +Ferguson +metals +reasoning +acids +hunt +pop +1946 +dirt +Texas +Keynes +conceptual +aiming +stating +technically +heading +economically +constituted +Union +maker +blowing +touching +tours +erected +ambitions +spare +chorus +Bond +bladder +settings +dividends +18th +Gaulle +unusually +phases +adapt +colitis +exploded +Nelson +civic +bells +gall +Macdonald +unwilling +retreat +booklet +enforce +defining +goodbye +meaningful +Gregory +pine +borrowed +bow +disturbing \ No newline at end of file diff --git a/setup/src/Magento/Setup/Fixtures/_files/orders_fixture_data.json b/setup/src/Magento/Setup/Fixtures/_files/orders_fixture_data.json new file mode 100644 index 0000000000000..59760c4e5e276 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/_files/orders_fixture_data.json @@ -0,0 +1,568 @@ +{ + "eav_entity_store": { + "entity_store_id": "'%productStoreId%'", + "entity_type_id": 5, + "store_id": "'%productStoreId%'", + "increment_prefix": "'%productStoreId%'", + "increment_last_id": "'%orderNumber%'", + "_query_suffix": " ON DUPLICATE KEY UPDATE `increment_last_id`='%orderNumber%'" + }, + "sales_order": { + "entity_id": "'%entityId%'", + "state": "'new'", + "status": "'pending'", + "coupon_code": "NULL", + "protect_code": "'272ecb'", + "shipping_description": "'Flat Rate - Fixed'", + "is_virtual": 0, + "store_id": "'%productStoreId%'", + "customer_id": "NULL", + "base_discount_amount": -1.7000, + "base_discount_canceled": "NULL", + "base_discount_invoiced": "NULL", + "base_discount_refunded": "NULL", + "base_grand_total": 25.3000, + "base_shipping_amount": 10.0000, + "base_shipping_canceled": "NULL", + "base_shipping_invoiced": "NULL", + "base_shipping_refunded": "NULL", + "base_shipping_tax_amount": 0.0000, + "base_shipping_tax_refunded": "NULL", + "base_subtotal": 17.0000, + "base_subtotal_canceled": "NULL", + "base_subtotal_invoiced": "NULL", + "base_subtotal_refunded": "NULL", + "base_tax_amount": 0.0000, + "base_tax_canceled": "NULL", + "base_tax_invoiced": "NULL", + "base_tax_refunded": "NULL", + "base_to_global_rate": 1.0000, + "base_to_order_rate": 1.0000, + "base_total_canceled": "NULL", + "base_total_invoiced": "NULL", + "base_total_invoiced_cost": "NULL", + "base_total_offline_refunded": "NULL", + "base_total_online_refunded": "NULL", + "base_total_paid": "NULL", + "base_total_qty_ordered": "NULL", + "base_total_refunded": "NULL", + "discount_amount": -1.7000, + "discount_canceled": "NULL", + "discount_invoiced": "NULL", + "discount_refunded": "NULL", + "grand_total": 25.3000, + "shipping_amount": 10.0000, + "shipping_canceled": "NULL", + "shipping_invoiced": "NULL", + "shipping_refunded": "NULL", + "shipping_tax_amount": 0.0000, + "shipping_tax_refunded": "NULL", + "store_to_base_rate": 0.0000, + "store_to_order_rate": 0.0000, + "subtotal": 17.0000, + "subtotal_canceled": "NULL", + "subtotal_invoiced": "NULL", + "subtotal_refunded": "NULL", + "tax_amount": 0.0000, + "tax_canceled": "NULL", + "tax_invoiced": "NULL", + "tax_refunded": "NULL", + "total_canceled": "NULL", + "total_invoiced": "NULL", + "total_offline_refunded": "NULL", + "total_online_refunded": "NULL", + "total_paid": "NULL", + "total_qty_ordered": 2.0000, + "total_refunded": "NULL", + "can_ship_partially": "NULL", + "can_ship_partially_item": "NULL", + "customer_is_guest": 1, + "customer_note_notify": 1, + "billing_address_id": 2, + "customer_group_id": 0, + "edit_increment": "NULL", + "email_sent": 1, + "send_email": 1, + "forced_shipment_with_invoice": "NULL", + "payment_auth_expiration": "NULL", + "quote_address_id": "NULL", + "quote_id": "'%entityId%'", + "shipping_address_id": 1, + "adjustment_negative": "NULL", + "adjustment_positive": "NULL", + "base_adjustment_negative": "NULL", + "base_adjustment_positive": "NULL", + "base_shipping_discount_amount": "NULL", + "base_subtotal_incl_tax": 17.0000, + "base_total_due": 25.3000, + "payment_authorization_amount": "NULL", + "shipping_discount_amount": "NULL", + "subtotal_incl_tax": 17.0000, + "total_due": 25.3000, + "weight": 2.0000, + "customer_dob": "NULL", + "increment_id": "'%orderNumber%'", + "applied_rule_ids": 1, + "base_currency_code": "'USD'", + "customer_email": "'%email%'", + "customer_firstname": "NULL", + "customer_lastname": "NULL", + "customer_middlename": "NULL", + "customer_prefix": "NULL", + "customer_suffix": "NULL", + "customer_taxvat": "NULL", + "discount_description": "NULL", + "ext_customer_id": "NULL", + "ext_order_id": "NULL", + "global_currency_code": "'USD'", + "hold_before_state": "NULL", + "hold_before_status": "NULL", + "order_currency_code": "'USD'", + "original_increment_id": "NULL", + "relation_child_id": "NULL", + "relation_child_real_id": "NULL", + "relation_parent_id": "NULL", + "relation_parent_real_id": "NULL", + "remote_ip": "'127.0.0.1'", + "shipping_method": "'flatrate_flatrate'", + "store_currency_code": "'USD'", + "store_name": "'%productStoreName%'", + "x_forwarded_for": "NULL", + "customer_note": "NULL", + "created_at": "'%time%'", + "updated_at": "'%time%'", + "total_item_count": "'%itemsPerOrder%'", + "customer_gender": "NULL", + "discount_tax_compensation_amount": 0.0000, + "base_discount_tax_compensation_amount": 0.0000, + "shipping_discount_tax_compensation_amount": 0.0000, + "base_shipping_discount_tax_compensation_amnt": "NULL", + "discount_tax_compensation_invoiced": "NULL", + "base_discount_tax_compensation_invoiced": "NULL", + "discount_tax_compensation_refunded": "NULL", + "base_discount_tax_compensation_refunded": "NULL", + "shipping_incl_tax": 10.0000, + "base_shipping_incl_tax": 10.0000, + "coupon_rule_name": "NULL", + "gift_message_id": "NULL" + }, + "sales_order_address": { + "entity_id": "'%orderAddressId%'", + "parent_id": "'%entityId%'", + "customer_address_id": "NULL", + "quote_address_id": "NULL", + "region_id": "1", + "customer_id": "NULL", + "fax": "NULL", + "region": "'%state%'", + "postcode": "'%zip%'", + "lastname": "'%lastName%'", + "street": "'%address%'", + "city": "'%city%'", + "email": "'%email%'", + "telephone": "'%phone%'", + "country_id": "'%country%'", + "firstname": "'%firstName%'", + "address_type": "'%addressType%'", + "prefix": "NULL", + "middlename": "NULL", + "suffix": "NULL", + "company": "'%company%'", + "vat_id": "NULL", + "vat_is_valid": "NULL", + "vat_request_id": "NULL", + "vat_request_date": "NULL", + "vat_request_success": "NULL" + }, + "sales_order_grid": { + "entity_id": "'%entityId%'", + "status": "'pending'", + "store_id": "'%productStoreId%'", + "store_name": "'%entityId%'", + "customer_id": "NULL", + "base_grand_total": 25.3000, + "base_total_paid": "NULL", + "grand_total": 25.3000, + "total_paid": "NULL", + "increment_id": "'%orderNumber%'", + "base_currency_code": "'USD'", + "order_currency_code": "'USD'", + "shipping_name": "''", + "billing_name": "''", + "created_at": "'%time%'", + "updated_at": "NULL" + }, + "sales_order_item": { + "item_id": "'%itemId%'", + "order_id": "'%entityId%'", + "parent_item_id": "'%parentItemId%'", + "quote_item_id": "'%itemId%'", + "store_id": "'%productStoreId%'", + "created_at": "'%time%'", + "updated_at": "'0000-00-00 00:00:00'", + "product_id": "'%productId%'", + "product_type": "'%productType%'", + "product_options": "'%productOptions%'", + "weight": 1.0000, + "is_virtual": "'0'", + "sku": "'%sku%'", + "name": "'%name%'", + "description": "NULL", + "applied_rule_ids": "'1'", + "additional_data": "NULL", + "is_qty_decimal": "'0'", + "no_discount": "'0'", + "qty_backordered": "NULL", + "qty_canceled": 0.0000, + "qty_invoiced": 0.0000, + "qty_ordered": 1.0000, + "qty_refunded": 0.0000, + "qty_shipped": 0.0000, + "base_cost": "NULL", + "price": 8.5000, + "base_price": 8.5000, + "original_price": 10.0000, + "base_original_price": 10.0000, + "tax_percent": 0.0000, + "tax_amount": 0.0000, + "base_tax_amount": 0.0000, + "tax_invoiced": 0.0000, + "base_tax_invoiced": 0.0000, + "discount_percent": 10.0000, + "discount_amount": 0.8500, + "base_discount_amount": 0.8500, + "discount_invoiced": 0.0000, + "base_discount_invoiced": 0.0000, + "amount_refunded": 0.0000, + "base_amount_refunded": 0.0000, + "row_total": 8.5000, + "base_row_total": 8.5000, + "row_invoiced": 0.0000, + "base_row_invoiced": 0.0000, + "row_weight": 1.0000, + "base_tax_before_discount": "NULL", + "tax_before_discount": "NULL", + "ext_order_item_id": "NULL", + "locked_do_invoice": "NULL", + "locked_do_ship": "NULL", + "price_incl_tax": 8.5000, + "base_price_incl_tax": 8.5000, + "row_total_incl_tax": 8.5000, + "base_row_total_incl_tax": 8.5000, + "discount_tax_compensation_amount": 0.0000, + "base_discount_tax_compensation_amount": 0.0000, + "discount_tax_compensation_invoiced": "NULL", + "base_discount_tax_compensation_invoiced": "NULL", + "discount_tax_compensation_refunded": "NULL", + "base_discount_tax_compensation_refunded": "NULL", + "tax_canceled": "NULL", + "discount_tax_compensation_canceled": "NULL", + "tax_refunded": "NULL", + "base_tax_refunded": "NULL", + "discount_refunded": "NULL", + "base_discount_refunded": "NULL", + "free_shipping": "'0'", + "gift_message_id": "NULL", + "gift_message_available": "NULL", + "weee_tax_applied": "NULL", + "weee_tax_applied_amount": "NULL", + "weee_tax_applied_row_amount": "NULL", + "weee_tax_disposition": "NULL", + "weee_tax_row_disposition": "NULL", + "base_weee_tax_applied_amount": "NULL", + "base_weee_tax_applied_row_amnt": "NULL", + "base_weee_tax_disposition": "NULL", + "base_weee_tax_row_disposition": "NULL" + }, + "sales_order_payment": { + "entity_id": "'%entityId%'", + "parent_id": "'%entityId%'", + "base_shipping_captured": "NULL", + "shipping_captured": "NULL", + "amount_refunded": "NULL", + "base_amount_paid": "NULL", + "amount_canceled": "NULL", + "base_amount_authorized": "NULL", + "base_amount_paid_online": "NULL", + "base_amount_refunded_online": "NULL", + "base_shipping_amount": 10.0000, + "shipping_amount": 10.0000, + "amount_paid": "NULL", + "amount_authorized": "NULL", + "base_amount_ordered": 25.3000, + "base_shipping_refunded": "NULL", + "shipping_refunded": "NULL", + "base_amount_refunded": "NULL", + "amount_ordered": 25.3000, + "base_amount_canceled": "NULL", + "quote_payment_id": "NULL", + "additional_data": "NULL", + "cc_exp_month": "NULL", + "cc_ss_start_year": "'0'", + "echeck_bank_name": "NULL", + "method": "'checkmo'", + "cc_debug_request_body": "NULL", + "cc_secure_verify": "NULL", + "protection_eligibility": "NULL", + "cc_approval": "NULL", + "cc_last_4": "NULL", + "cc_status_description": "NULL", + "echeck_type": "NULL", + "cc_debug_response_serialized": "NULL", + "cc_ss_start_month": "'0'", + "echeck_account_type": "NULL", + "last_trans_id": "NULL", + "cc_cid_status": "NULL", + "cc_owner": "NULL", + "cc_type": "NULL", + "po_number": "NULL", + "cc_exp_year": "NULL", + "cc_status": "NULL", + "echeck_routing_number": "NULL", + "account_status": "NULL", + "anet_trans_method": "NULL", + "cc_debug_response_body": "NULL", + "cc_ss_issue": "NULL", + "echeck_account_name": "NULL", + "cc_avs_status": "NULL", + "cc_number_enc": "NULL", + "cc_trans_id": "NULL", + "address_status": "NULL", + "additional_information": "'a:1:{s:53:\"a:1:{s:12:\"method_title\";s:19:\"Check \/ Money order\";}\";N;}'" + }, + "sales_order_status_history": { + "entity_id": "'%entityId%'", + "parent_id": "'%entityId%'", + "is_customer_notified": "1", + "is_visible_on_front": "0", + "comment": "NULL", + "status": "'pending'", + "created_at": "'%time%'", + "entity_name": "'order'" + }, + "quote": { + "entity_id": "'%entityId%'", + "store_id": "'%productStoreId%'", + "created_at": "'%time%'", + "updated_at": "'1970-01-01 03:00:00'", + "converted_at": "NULL", + "is_active": 0, + "is_virtual": 0, + "is_multi_shipping": 0, + "items_count": "'%itemsPerOrder%'", + "items_qty": "'%itemsPerOrder%'", + "orig_order_id": 0, + "store_to_base_rate": 0.0000, + "store_to_quote_rate": 0.0000, + "base_currency_code": "'USD'", + "store_currency_code": "'USD'", + "quote_currency_code": "'USD'", + "grand_total": 25.3000, + "base_grand_total": 25.3000, + "checkout_method": "'guest'", + "customer_id": "NULL", + "customer_tax_class_id": 3, + "customer_group_id": 0, + "customer_email": "'%email%'", + "customer_prefix": "NULL", + "customer_firstname": "NULL", + "customer_middlename": "NULL", + "customer_lastname": "NULL", + "customer_suffix": "NULL", + "customer_dob": "NULL", + "customer_note": "NULL", + "customer_note_notify": 1, + "customer_is_guest": 1, + "remote_ip": "'127.0.0.1'", + "applied_rule_ids": "'1'", + "reserved_order_id": "NULL", + "password_hash": "NULL", + "coupon_code": "NULL", + "global_currency_code": "'USD'", + "base_to_global_rate": 1.0000, + "base_to_quote_rate": 1.0000, + "customer_taxvat": "NULL", + "customer_gender": "NULL", + "subtotal": 17.0000, + "base_subtotal": 17.0000, + "subtotal_with_discount": 15.3000, + "base_subtotal_with_discount": 15.3000, + "is_changed": 1, + "trigger_recollect": 0, + "ext_shipping_info": "NULL", + "is_persistent": 0, + "gift_message_id": "NULL", + "_table": "quote", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote" + }, + "quote_address": { + "address_id": "'%orderAddressId%'", + "quote_id": "'%entityId%'", + "created_at": "'%time%'", + "updated_at": "'1970-01-01 03:00:00'", + "customer_id": "NULL", + "save_in_address_book": 1, + "customer_address_id": "NULL", + "address_type": "'%addressType%'", + "email": "'%email%'", + "prefix": "NULL", + "firstname": "'%firstName%'", + "middlename": "NULL", + "lastname": "'%lastName%'", + "suffix": "NULL", + "company": "'%company%'", + "street": "'%address%'", + "city": "'%city%'", + "region": "'%state%'", + "region_id": "1", + "postcode": "'%zip%'", + "country_id": "'%country%'", + "telephone": "'%phone%'", + "fax": "NULL", + "same_as_billing": 0, + "collect_shipping_rates": 0, + "shipping_method": "'flatrate_flatrate'", + "shipping_description": "'Flat Rate - Fixed'", + "weight": 0.0000, + "subtotal": 0.0000, + "base_subtotal": 0.0000, + "subtotal_with_discount": 0.0000, + "base_subtotal_with_discount": 0.0000, + "tax_amount": 0.0000, + "base_tax_amount": 0.0000, + "shipping_amount": 0.0000, + "base_shipping_amount": 0.0000, + "shipping_tax_amount": "NULL", + "base_shipping_tax_amount": "NULL", + "discount_amount": 0.0000, + "base_discount_amount": 0.0000, + "grand_total": 0.0000, + "base_grand_total": 0.0000, + "customer_notes": "NULL", + "applied_taxes": "NULL", + "discount_description": "NULL", + "shipping_discount_amount": "NULL", + "base_shipping_discount_amount": "NULL", + "subtotal_incl_tax": 0.0000, + "base_subtotal_total_incl_tax": "NULL", + "discount_tax_compensation_amount": 0.0000, + "base_discount_tax_compensation_amount": 0.0000, + "shipping_discount_tax_compensation_amount": 0.0000, + "base_shipping_discount_tax_compensation_amnt": "NULL", + "shipping_incl_tax": "NULL", + "base_shipping_incl_tax": "NULL", + "free_shipping": 0, + "vat_id": "NULL", + "vat_is_valid": "NULL", + "vat_request_id": "NULL", + "vat_request_date": "NULL", + "vat_request_success": "NULL", + "gift_message_id": "NULL", + "_table": "quote_address", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote\\Address" + }, + "quote_payment": { + "payment_id": "'%entityId%'", + "quote_id": "'%entityId%'", + "created_at": "'%time%'", + "updated_at": "'1970-01-01 03:00:00'", + "method": "'checkmo'", + "cc_type": "NULL", + "cc_number_enc": "NULL", + "cc_last_4": "NULL", + "cc_cid_enc": "NULL", + "cc_owner": "NULL", + "cc_exp_month": "NULL", + "cc_exp_year": "0", + "cc_ss_owner": "NULL", + "cc_ss_start_month": "0", + "cc_ss_start_year": "0", + "po_number": "NULL", + "additional_data": "NULL", + "cc_ss_issue": "NULL", + "additional_information": "NULL", + "_table": "quote_payment", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote\\Payment" + }, + "quote_shipping_rate": { + "rate_id": "'%orderAddressId%'", + "address_id": "'%orderAddressId%'", + "created_at": "'%time%'", + "updated_at": "'1970-01-01 03:00:00'", + "carrier": "'flatrate'", + "carrier_title": "'Flat Rate'", + "code": "'flatrate_flatrate'", + "method": "'flatrate'", + "method_description": "NULL", + "price": "10.0000", + "error_message": "NULL", + "method_title": "'Fixed'", + "_table": "quote_shipping_rate", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote\\Address\\Rate" + }, + "quote_item": { + "item_id": "'%itemId%'", + "quote_id": "'%entityId%'", + "created_at": "'1970-01-01 03:00:00'", + "updated_at": "'1970-01-01 03:00:00'", + "product_id": "'%productId%'", + "store_id": "'%productStoreId%'", + "parent_item_id": "%parentItemId%", + "is_virtual": "0", + "sku": "'%sku%'", + "name": "'%name%'", + "description": "NULL", + "applied_rule_ids": "'1'", + "additional_data": "NULL", + "is_qty_decimal": "0", + "no_discount": "0", + "weight": "1.0000", + "qty": "1.0000", + "price": "8.5000", + "base_price": "8.5000", + "custom_price": "NULL", + "discount_percent": "10.0000", + "discount_amount": "0.8500", + "base_discount_amount": "0.8500", + "tax_percent": "0.0000", + "tax_amount": "0.0000", + "base_tax_amount": "0.0000", + "row_total": "8.5000", + "base_row_total": "8.5000", + "row_total_with_discount": "0.0000", + "row_weight": "1.0000", + "product_type": "'simple'", + "base_tax_before_discount": "NULL", + "tax_before_discount": "NULL", + "original_custom_price": "NULL", + "redirect_url": "NULL", + "base_cost": "NULL", + "price_incl_tax": "8.5000", + "base_price_incl_tax": "8.5000", + "row_total_incl_tax": "8.5000", + "base_row_total_incl_tax": "8.5000", + "discount_tax_compensation_amount": "0.0000", + "base_discount_tax_compensation_amount": "0.0000", + "free_shipping": "0", + "gift_message_id": "NULL", + "weee_tax_applied": "NULL", + "weee_tax_applied_amount": "NULL", + "weee_tax_applied_row_amount": "NULL", + "weee_tax_disposition": "NULL", + "weee_tax_row_disposition": "NULL", + "base_weee_tax_applied_amount": "NULL", + "base_weee_tax_applied_row_amnt": "NULL", + "base_weee_tax_disposition": "NULL", + "base_weee_tax_row_disposition": "NULL", + "_table": "quote_item", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote\\Item" + }, + "quote_item_option": { + "item_id": "'%itemId%'", + "product_id": "'%productId%'", + "code": "'%code%'", + "value": "'%value%'", + "_table": "quote_item_option", + "_resource": "Magento\\Quote\\Model\\ResourceModel\\Quote\\Item\\Option" + } +} diff --git a/setup/src/Magento/Setup/Fixtures/tax_rates.csv b/setup/src/Magento/Setup/Fixtures/_files/tax_rates.csv similarity index 100% rename from setup/src/Magento/Setup/Fixtures/tax_rates.csv rename to setup/src/Magento/Setup/Fixtures/_files/tax_rates.csv diff --git a/setup/src/Magento/Setup/Fixtures/_files/tax_rates_small.csv b/setup/src/Magento/Setup/Fixtures/_files/tax_rates_small.csv new file mode 100644 index 0000000000000..bc68322ec9e78 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/_files/tax_rates_small.csv @@ -0,0 +1,133 @@ +"Code","Country","State","Zip/Post Code","Rate","Zip/Post is Range","Range From","Range To","default" +"US-AL","US","AL","*","4","","","","" +"US-AK","US","AK","*","0","","","","" +"US-AZ","US","AZ","*","6.6","","","","" +"US-AR","US","AR","*","6","","","","" +"US-CA","US","CA","*","8.25","","","","" +"US-CO","US","CO","*","2.9","","","","" +"US-CT","US","CT","*","6","","","","" +"US-DE","US","DE","*","0","","","","" +"US-FL","US","FL","*","6","","","","" +"US-GA","US","GA","*","4","","","","" +"US-HI","US","HI","*","4","","","","" +"US-ID","US","ID","*","6","","","","" +"US-IL","US","IL","*","6.25","","","","" +"US-IN","US","IN","*","7","","","","" +"US-IA","US","IA","*","6","","","","" +"US-KS","US","KS","*","6.3","","","","" +"US-KY","US","KY","*","6","","","","" +"US-LA","US","LA","*","4","","","","" +"US-ME","US","ME","*","5","","","","" +"US-MD","US","MD","*","6","","","","" +"US-MA","US","MA","*","6.25","","","","" +"US-MI","US","MI","*","6","","","","" +"US-MN","US","MN","*","6.875","","","","" +"US-MS","US","MS","*","7","","","","" +"US-MO","US","MO","*","4.225","","","","" +"US-MT","US","MT","*","0","","","","" +"US-NE","US","NE","*","5.5","","","","" +"US-NV","US","NV","*","6.85","","","","" +"US-NH","US","NH","*","0","","","","" +"US-NJ","US","NJ","*","7","","","","" +"US-NM","US","NM","*","5.125","","","","" +"US-NY","US","NY","*","4","","","","" +"US-NC","US","NC","*","5.75","","","","" +"US-ND","US","ND","*","5","","","","" +"US-OH","US","OH","*","5.5","","","","" +"US-OK","US","OK","*","4.5","","","","" +"US-OR","US","OR","*","0","","","","" +"US-PA","US","PA","*","6","","","","" +"US-RI","US","RI","*","7","","","","" +"US-SC","US","SC","*","6","","","","" +"US-SD","US","SD","*","4","","","","" +"US-TN","US","TN","*","7","","","","" +"US-TX","US","TX","*","6.25","","","","" +"US-UT","US","UT","*","4.7","","","","" +"US-VT","US","VT","*","6","","","","" +"US-VA","US","VA","*","4","","","","" +"US-WA","US","WA","*","6.5","","","","" +"US-WV","US","WV","*","6","","","","" +"US-WI","US","WI","*","5","","","","" +"US-WY","US","WY","*","4","","","","" +"Austria (standard)","AT","*","","20.0000","","","","" +"Austria (reduced: food / books / pharma / hotels)","AT","*","","10.0000","","","","" +"Belgium (standard)","BE","*","","21.0000","","","","" +"Belgium (reduced: restaurants)","BE","*","","12.0000","","","","" +"Belgium (reduced: food / books/ pharma / medical / hotels)","BE","*","","6.0000","","","","" +"Bulgaria (standard)","BG","*","","20.0000","","","","" +"Bulgaria (reduced: hotels)","BG","*","","9.0000","","","","" +"Croatia (standard)","HR","*","","25.0000","","","","" +"Croatia (reduced: hotels / newspapers)","HR","*","","13.0000","","","","" +"Cyprus (standard)","CY","*","","19.0000","","","","" +"Cyprus (reduced: hotels)","CY","*","","9.0000","","","","" +"Cyprus (reduced: food / books / pharma / medical)","CY","*","","5.0000","","","","" +"Czech Republic (standard)","CZ","*","","21.0000","","","","" +"Czech Republic (reduced: food / medical / pharma / hotels)","CZ","*","","15.0000","","","","" +"Czech Republic (reduced: pharma / books)","CZ","*","","10.0000","","","","" +"Denmark (standard)","DK","*","","25.0000","","","","" +"Estonia (standard)","EE","*","","20.0000","","","","" +"Estonia (reduced: books / pharma / medical / hotels)","EE","*","","9.0000","","","","" +"Finland (standard)","FI","*","","24.0000","","","","" +"Finland (reduced: restaurants)","FI","*","","14.0000","","","","" +"Finland (reduced: books / pharma / hotels)","FI","*","","10.0000","","","","" +"France (standard)","FR","*","","20.0000","","","","" +"France (reduced: pharma / hotels / restaurants)","FR","*","","10.0000","","","","" +"France (reduced: medicals / food / books)","FR","*","","5.5000","","","","" +"France (reduced: newspapers / pharma)","FR","*","","2.1000","","","","" +"Germany (standard)","DE","*","","19.0000","","","","" +"Germany (reduced: food / medical / books / hotels)","DE","*","","7.0000","","","","" +"Greece (standard)","GR","*","","24.0000","","","","" +"Greece (reduced: food / pharma / medical)","GR","*","","13.0000","","","","" +"Greece (reduced: books / hotels)","GR","*","","6.0000","","","","" +"Hungary (standard)","HU","*","","27.0000","","","","" +"Hungary (reduced: food / hotels)","HU","*","","18.0000","","","","" +"Hungary (reduced: books / medical)","HU","*","","5.0000","","","","" +"Ireland (standard)","IE","*","","23.0000","","","","" +"Ireland (reduced: medical)","IE","*","","13.5000","","","","" +"Ireland (reduced: newspapers / hotels / restaurants)","IE","*","","9.0000","","","","" +"Ireland (reduced: food)","IE","*","","4.8000","","","","" +"Ireland (free: books / medical / children clothing)","IE","*","","0.0000","","","","" +"Italy (standard)","IT","*","","22.0000","","","","" +"Italy (reduced: transport / hotels / restaurants)","IT","*","","10.0000","","","","" +"Italy (reduced: food / medical / books)","IT","*","","4.0000","","","","" +"Latvia (standard)","LV","*","","21.0000","","","","" +"Latvia (reduced: books / pharma / medical / hotels)","LV","*","","12.0000","","","","" +"Lithuania (standard)","LT","*","","21.0000","","","","" +"Lithuania (reduced: books)","LT","*","","9.0000","","","","" +"Lithuania (reduced: medical)","LT","*","","5.0000","","","","" +"Luxembourg (standard)","LU","*","","17.0000","","","","" +"Luxembourg (reduced: wine / advertising)","LU","*","","14.0000","","","","" +"Luxembourg (reduced: bikes / domestic)","LU","*","","8.0000","","","","" +"Luxembourg (reduced: food / books / pharma / medical / hotels / restaurants)","LU","*","","3.0000","","","","" +"Malta (standard)","MT","*","","18.0000","","","","" +"Malta (reduced: hotels)","MT","*","","7.0000","","","","" +"Malta (reduced: books / medical)","MT","*","","5.0000","","","","" +"Malta (reduced: food / pharma)","MT","*","","0.0000","","","","" +"Netherlands (standard)","NL","*","","21.0000","","","","" +"Netherlands (reduced: food / books / pharma / medical / hotels)","NL","*","","6.0000","","","","" +"Poland (standard)","PL","*","","23.0000","","","","" +"Poland (reduced: pharma / medical / hotels / restaurants)","PL","*","","8.0000","","","","" +"Poland (reduced: food)","PL","*","","5.0000","","","","" +"Portugal (standard)","PT","*","","23.0000","","","","" +"Portugal (reduced: food)","PT","*","","13.0000","","","","" +"Portugal (reduced: food / books / pharma / medical / hotels)","PT","*","","6.0000","","","","" +"Romania (standard)","RO","*","","24.0000","","","","" +"Romania (reduced: social housing)","RO","*","","5.0000","","","","" +"Romania (reduced: books / pharma / medical / hotels)","RO","*","","9.0000","","","","" +"Slovakia (standard)","SK","*","","20.0000","","","","" +"Slovakia (reduced: books / food / medical / pharma)","SK","*","","10.0000","","","","" +"Slovenia (standard)","SI","*","","22.0000","","","","" +"Slovenia (reduced: food / books / pharma / medical / hotels)","SI","*","","9.5000","","","","" +"Spain (standard)","ES","*","","21.0000","","","","" +"Spain (reduced: medical / pharma)","ES","*","","10.0000","","","","" +"Spain (reduced: food / newspapers)","ES","*","","4.0000","","","","" +"Spain (Tenerife) (standard)","ES","Santa Cruz de Tenerife","","0.0000","","","","" +"Spain (Las Palmas) (standard)","ES","Las Palmas","*","0.0000","","","","" +"Spain (Melilla) (standard)","ES","Melilla","","0.0000","","","","" +"Spain (Ceuta) (standard)","ES","Ceuta","*","0.0000","","","","" +"Sweden (standard)","SE","*","","25.0000","","","","" +"Sweden (reduced: food)","SE","*","","12.0000","","","","" +"Sweden (reduced: books)","SE","*","","6.0000","","","","" +"United Kingdom (standard)","GB","*","","20.0000","","","","" +"United Kingdom (reduced: property renovations)","GB","*","","5.0000","","","","" +"United Kingdom (reduced: food / books / pharma / medical)","GB","*","","0.0000","","","","" \ No newline at end of file diff --git a/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php new file mode 100644 index 0000000000000..683674396cfa0 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php @@ -0,0 +1,24 @@ + mt_rand(10000, 99999) + ]; + } +} diff --git a/setup/src/Magento/Setup/Model/AdminAccount.php b/setup/src/Magento/Setup/Model/AdminAccount.php index 6605052cede1e..6acb57dcf6cbb 100644 --- a/setup/src/Magento/Setup/Model/AdminAccount.php +++ b/setup/src/Magento/Setup/Model/AdminAccount.php @@ -1,6 +1,6 @@ getHeaders() as $key) { if (isset($row[$key])) { if (is_callable($row[$key])) { - $row[$key] = call_user_func($row[$key], $index); + $row[$key] = call_user_func($row[$key], $index, $generatorKey); } else { $row[$key] = str_replace('%s', $index, $row[$key]); } diff --git a/setup/src/Magento/Setup/Model/ConfigGenerator.php b/setup/src/Magento/Setup/Model/ConfigGenerator.php index 140f5a739889c..b070da8ada199 100644 --- a/setup/src/Magento/Setup/Model/ConfigGenerator.php +++ b/setup/src/Magento/Setup/Model/ConfigGenerator.php @@ -1,6 +1,6 @@ set( - ObjectManagerFactory::CONFIG_PATH_DEFINITION_FORMAT, - $data[ConfigOptionsListConstants::INPUT_KEY_DEFINITION_FORMAT] - ); - } - - return $configData; + return null; } /** diff --git a/setup/src/Magento/Setup/Model/ConfigModel.php b/setup/src/Magento/Setup/Model/ConfigModel.php index cc956ad443ebd..f1fd56abede81 100644 --- a/setup/src/Magento/Setup/Model/ConfigModel.php +++ b/setup/src/Magento/Setup/Model/ConfigModel.php @@ -1,6 +1,6 @@ groupCollectionFactory = $groupCollectionFactory; + $this->addressDataGenerator = $addressDataGenerator; + $this->config = $config; + } + + /** + * Generate customer data by index + * + * @param int $customerId + * @return array + */ + public function generate($customerId) + { + return [ + 'customer' => [ + 'email' => sprintf('user_%s@example.com', $customerId), + 'group_id' => $this->getGroupIdForCustomer($customerId) + ], + + 'addresses' => $this->generateAddresses(), + ]; + } + + /** + * Get customer group id for customer + * @param int $customerId + * @return int + */ + private function getGroupIdForCustomer($customerId) + { + if (!$this->customerGroupIds) { + $this->customerGroupIds = $this->groupCollectionFactory->create()->getAllIds(); + } + + return $this->customerGroupIds[$customerId % count($this->customerGroupIds)]; + } + + /** + * Generate customer addresses with distribution + * 50% as shipping address + * 50% as billing address + * + * @return array + */ + private function generateAddresses() + { + $addresses = []; + $addressesCount = $this->config['addresses-count']; + + while ($addressesCount) { + $addresses[] = $this->addressDataGenerator->generateAddress(); + $addressesCount--; + } + + return $addresses; + } +} diff --git a/setup/src/Magento/Setup/Model/Customer/CustomerDataGeneratorFactory.php b/setup/src/Magento/Setup/Model/Customer/CustomerDataGeneratorFactory.php new file mode 100644 index 0000000000000..5ef7560cacc50 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Customer/CustomerDataGeneratorFactory.php @@ -0,0 +1,44 @@ +objectManager = $objectManager; + } + + /** + * Create CustomerGenerator instance with specified configuration + * + * @param array $config + * @return \Magento\Setup\Model\Customer\CustomerDataGenerator + */ + public function create(array $config) + { + return $this->objectManager->create( + \Magento\Setup\Model\Customer\CustomerDataGenerator::class, + [ + 'addressGenerator' => $this->objectManager->create( + \Magento\Setup\Model\Address\AddressDataGenerator::class + ), + 'config' => $config + ] + ); + } +} diff --git a/setup/src/Magento/Setup/Model/DataGenerator.php b/setup/src/Magento/Setup/Model/DataGenerator.php new file mode 100644 index 0000000000000..4952a05e37e7f --- /dev/null +++ b/setup/src/Magento/Setup/Model/DataGenerator.php @@ -0,0 +1,87 @@ +dictionaryFile = $dictionaryFile; + $this->readData(); + $this->generatedValues = []; + } + + /** + * Read data from file. + * + * @return void + */ + protected function readData() + { + $f = fopen($this->dictionaryFile, 'r'); + while (!feof($f) && is_array($line = fgetcsv($f))) { + $this->dictionaryData[] = $line[0]; + } + } + + /** + * Generate string of random word data. + * + * @param int $minAmountOfWords + * @param int $maxAmountOfWords + * @param string|null $key + * @return string + */ + public function generate($minAmountOfWords, $maxAmountOfWords, $key = null) + { + $numberOfWords = mt_rand($minAmountOfWords, $maxAmountOfWords); + $result = ''; + + if ($key === null || !array_key_exists($key, $this->generatedValues)) { + for ($i = 0; $i < $numberOfWords; $i++) { + $result .= ' ' . $this->dictionaryData[mt_rand(0, count($this->dictionaryData) - 1)]; + } + $result = trim($result); + + if ($key !== null) { + $this->generatedValues[$key] = $result; + } + } else { + $result = $this->generatedValues[$key]; + } + return $result; + } +} diff --git a/setup/src/Magento/Setup/Model/DateTime/DateTimeProvider.php b/setup/src/Magento/Setup/Model/DateTime/DateTimeProvider.php index 1ac281a8d37c4..daf39705592f4 100644 --- a/setup/src/Magento/Setup/Model/DateTime/DateTimeProvider.php +++ b/setup/src/Magento/Setup/Model/DateTime/DateTimeProvider.php @@ -1,6 +1,6 @@ defaultDescription = $defaultDescription; + } + + /** + * @param int $entityIndex + * @return string + */ + public function generate($entityIndex) + { + return sprintf($this->defaultDescription, $entityIndex); + } +} diff --git a/setup/src/Magento/Setup/Model/DependencyReadinessCheck.php b/setup/src/Magento/Setup/Model/DependencyReadinessCheck.php index 3845def7d2130..b485c574af2c5 100644 --- a/setup/src/Magento/Setup/Model/DependencyReadinessCheck.php +++ b/setup/src/Magento/Setup/Model/DependencyReadinessCheck.php @@ -1,6 +1,6 @@ paragraphGenerator = $paragraphGenerator; + $this->mixinManager = $mixinManager; + $this->descriptionConfig = $descriptionConfig; + } + + /** + * Generate description and apply mixin to it + * + * @return string + */ + public function generate() + { + $description = $this->generateRawDescription(); + + if (isset($this->descriptionConfig['mixin'])) { + $description = $this->mixinManager->apply($description, $this->descriptionConfig['mixin']['tags']); + } + + return $description; + } + + /** + * Generate raw description without mixin + * + * @return string + */ + private function generateRawDescription() + { + $paragraphsCount = mt_rand( + $this->descriptionConfig['paragraphs']['count-min'], + $this->descriptionConfig['paragraphs']['count-max'] + ); + $descriptionParagraphs = ''; + + while ($paragraphsCount) { + $descriptionParagraphs .= $this->paragraphGenerator->generate(); + $descriptionParagraphs .= PHP_EOL; + $paragraphsCount--; + } + + $descriptionParagraphs = rtrim($descriptionParagraphs); + + return $descriptionParagraphs; + } +} diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php new file mode 100644 index 0000000000000..aaeb390d540e6 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php @@ -0,0 +1,58 @@ +sentenceGenerator = $sentenceGenerator; + $this->paragraphConfig = $paragraphConfig; + } + + /** + * Generate paragraph for description + * + * @return string + */ + public function generate() + { + $sentencesCount = mt_rand( + $this->paragraphConfig['sentences']['count-min'], + $this->paragraphConfig['sentences']['count-max'] + ); + $sentences = ''; + + while ($sentencesCount) { + $sentences .= $this->sentenceGenerator->generate(); + $sentences .= ' '; + $sentencesCount--; + } + + $sentences = rtrim($sentences); + + return $sentences; + } +} diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php new file mode 100644 index 0000000000000..76f320b74c6f0 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php @@ -0,0 +1,56 @@ +dictionary = $dictionary; + $this->sentenceConfig = $sentenceConfig; + } + + /** + * Generate sentence for description + * + * @return string + */ + public function generate() + { + $sentenceWordsCount = mt_rand( + $this->sentenceConfig['words']['count-min'], + $this->sentenceConfig['words']['count-max'] + ); + $sentence = ''; + + while ($sentenceWordsCount) { + $sentence .= $this->dictionary->getRandWord(); + $sentence .= ' '; + $sentenceWordsCount--; + } + + return ucfirst(rtrim($sentence)) . '.'; + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php new file mode 100644 index 0000000000000..6b1f3039bc1c4 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php @@ -0,0 +1,55 @@ +randomWordSelector = $randomWordSelector; + $this->wordWrapper = $wordWrapper; + } + + /** + * Add tag to text at random positions + * + * @param string $text + * @return string + */ + public function apply($text) + { + if (empty(strip_tags(trim($text)))) { + return $text; + } + + $rawText = strip_tags($text); + + return $this->wordWrapper->wrapWords( + $text, + $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + '%s' + ); + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/BrakeMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/BrakeMixin.php new file mode 100644 index 0000000000000..12a492f6ec752 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/BrakeMixin.php @@ -0,0 +1,26 @@ + tag to text after each new line (\r\n) + * + * @param string $text + * @return string + */ + public function apply($text) + { + return implode( + PHP_EOL . '
    ' . PHP_EOL, + explode(PHP_EOL, trim($text)) + ); + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/DescriptionMixinInterface.php b/setup/src/Magento/Setup/Model/Description/Mixin/DescriptionMixinInterface.php new file mode 100644 index 0000000000000..88422e8038cf8 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/DescriptionMixinInterface.php @@ -0,0 +1,20 @@ + header with text before each new line (\r\n) + * + * @param string $text + * @return string + */ + public function apply($text) + { + $paragraphs = explode(PHP_EOL, trim($text)); + $magicLengthNumber = 10; + + foreach ($paragraphs as &$paragraph) { + $rawText = trim(strip_tags($paragraph)); + if (empty($rawText)) { + continue; + } + + $headerText = substr($rawText, 0, strpos($rawText, ' ', $magicLengthNumber)); + $paragraph = '

    ' . $headerText . '

    ' . PHP_EOL . $paragraph; + } + + return implode(PHP_EOL, $paragraphs); + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php new file mode 100644 index 0000000000000..77ac097f6f524 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php @@ -0,0 +1,36 @@ +randomWordSelector = $randomWordSelector; + $this->wordWrapper = $wordWrapper; + } + + /** + * Add tag to text at random positions + * + * @param string $text + * @return string + */ + public function apply($text) + { + if (empty(strip_tags(trim($text)))) { + return $text; + } + + $rawText = strip_tags($text); + + return $this->wordWrapper->wrapWords( + $text, + $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + '%s' + ); + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/MixinFactory.php b/setup/src/Magento/Setup/Model/Description/Mixin/MixinFactory.php new file mode 100644 index 0000000000000..b3de1c390dd33 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/MixinFactory.php @@ -0,0 +1,76 @@ + SpanMixin::class, + self::BOLD_MIXIN => BoldMixin::class, + self::BRAKE_MIXIN => BrakeMixin::class, + self::PARAGRAPH_MIXIN => ParagraphMixin::class, + self::HEADER_MIXIN => HeaderMixin::class, + self::ITALIC_MIXIN => ItalicMixin::class, + ]; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @throws \Magento\Setup\Exception + */ + public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * Create mixin by type + * + * @param string $mixinType + * @return \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface + * @throws \InvalidArgumentException + */ + public function create($mixinType) + { + if (!isset($this->typeClassMap[$mixinType])) { + throw new \InvalidArgumentException(sprintf('Undefined mixin type: %s.', $mixinType)); + } + + $mixin = $this->objectManager->get($this->typeClassMap[$mixinType]); + + if (!$mixin instanceof \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface) { + throw new \InvalidArgumentException( + sprintf( + 'Class "%s" must implement \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface.', + get_class($mixin) + ) + ); + } + + return $mixin; + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/ParagraphMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/ParagraphMixin.php new file mode 100644 index 0000000000000..09ab154dbdbd0 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/ParagraphMixin.php @@ -0,0 +1,28 @@ +

    tags + * + * @param string $text + * @return string + */ + public function apply($text) + { + return '

    ' + . implode( + '

    ' . PHP_EOL . '

    ', + explode(PHP_EOL, trim($text)) + ) + . '

    '; + } +} diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php new file mode 100644 index 0000000000000..b1fb1ecb25475 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php @@ -0,0 +1,55 @@ +randomWordSelector = $randomWordSelector; + $this->wordWrapper = $wordWrapper; + } + + /** + * Add tag to text at random positions + * + * @param string $text + * @return string + */ + public function apply($text) + { + if (empty(strip_tags(trim($text)))) { + return $text; + } + + $rawText = strip_tags($text); + + return $this->wordWrapper->wrapWords( + $text, + $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + '%s' + ); + } +} diff --git a/setup/src/Magento/Setup/Model/Description/MixinManager.php b/setup/src/Magento/Setup/Model/Description/MixinManager.php new file mode 100644 index 0000000000000..1dd627c322ec4 --- /dev/null +++ b/setup/src/Magento/Setup/Model/Description/MixinManager.php @@ -0,0 +1,41 @@ +mixinFactory = $mixinFactory; + } + + /** + * Apply list of mixin to description + * + * @param string $description + * @param array $mixinList + * @return mixed + */ + public function apply($description, array $mixinList) + { + foreach ($mixinList as $mixinType) { + $description = $this->mixinFactory->create($mixinType)->apply($description); + } + + return $description; + } +} diff --git a/setup/src/Magento/Setup/Model/DescriptionGeneratorInterface.php b/setup/src/Magento/Setup/Model/DescriptionGeneratorInterface.php new file mode 100644 index 0000000000000..2db798f8c1a33 --- /dev/null +++ b/setup/src/Magento/Setup/Model/DescriptionGeneratorInterface.php @@ -0,0 +1,20 @@ +dictionaryFilePath = $dictionaryFilePath; + } + + /** + * Returns random word from dictionary + * + * @return string + */ + public function getRandWord() + { + if ($this->dictionary === null) { + $this->readDictionary(); + } + + $randIndex = mt_rand(0, count($this->dictionary) - 1); + return trim($this->dictionary[$randIndex]); + } + + /** + * Read dictionary file + * + * @return void + * @throws \Magento\Setup\Exception + */ + private function readDictionary() + { + if (!is_readable($this->dictionaryFilePath)) { + throw new \Magento\Setup\Exception( + sprintf('Description file %s not found or is not readable', $this->dictionaryFilePath) + ); + } + + $rows = file($this->dictionaryFilePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + if ($rows === false) { + throw new \Magento\Setup\Exception( + sprintf('Error occurred while reading dictionary file %s', $this->dictionaryFilePath) + ); + } + + if (empty($rows)) { + throw new \Magento\Setup\Exception( + sprintf('Dictionary file %s is empty', $this->dictionaryFilePath) + ); + } + + $this->dictionary = \SplFixedArray::fromArray($rows); + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductGenerator.php new file mode 100644 index 0000000000000..44efd9ff208fe --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductGenerator.php @@ -0,0 +1,119 @@ + simple product sku pattern, which will be used as configurable variation, + * '_bundle_options' => amount of options per bundle product, + * '_bundle_products_per_option' => amount of simple products per each option, + * ] + * @see ProductGenerator + * @see BundleProductTemplateGenerator + */ +class BundleProductGenerator +{ + /** + * @var ProductGeneratorFactory + */ + private $productGeneratorFactory; + + /** + * @param ProductGeneratorFactory $productGeneratorFactory + */ + public function __construct(ProductGeneratorFactory $productGeneratorFactory) + { + $this->productGeneratorFactory = $productGeneratorFactory; + } + + /** + * Generate bundle products products + * + * @param int $products + * @param array $fixtureMap + * @return void + */ + public function generate($products, $fixtureMap) + { + $this->productGeneratorFactory->create([ + 'customTableMap' => [ + 'catalog_product_bundle_option_value' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['option_id'] = $this->generateOptionId($bind['option_id'], $entityNumber, $fixture); + } + return $binds; + }, + ], + 'catalog_product_bundle_selection' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['option_id'] = $this->generateOptionId($bind['option_id'], $entityNumber, $fixture); + $bind['parent_product_id'] = $productId; + $simpleProductId = $this->generateSimpleProductId( + $bind['product_id'], + $entityNumber, + $fixture + ); + $bind['product_id'] = $simpleProductId; + $bind['selection_price_value'] = $fixture['price']($simpleProductId); + $bind['selection_price_type'] = $fixture['priceType']($simpleProductId); + } + return $binds; + }, + ], + 'catalog_product_relation' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['parent_id'] = $productId; + $bind['child_id'] = $this->generateSimpleProductId( + $bind['child_id'], + $entityNumber, + $fixture + ); + } + return $binds; + }, + ], + ] + ])->generate($products, $fixtureMap); + } + + /** + * Generate value of option_id for $entityNumber bundle product based on previous option_id + * + * @param int $previousOptionId + * @param int $entityNumber + * @param array $fixture + * @return int + */ + private function generateOptionId($previousOptionId, $entityNumber, array $fixture) + { + return $previousOptionId + $entityNumber * $fixture['_bundle_options'] + $fixture['_bundle_options']; + } + + /** + * Generate value of simple product id which is used for $entityNumber bundle product as option item + * + * @param int $previousProductId + * @param int $entityNumber + * @param array $fixture + * @return mixed + */ + private function generateSimpleProductId($previousProductId, $entityNumber, array $fixture) + { + return $previousProductId + + $entityNumber * $fixture['_bundle_products_per_option'] * $fixture['_bundle_options']; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductTemplateGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductTemplateGenerator.php new file mode 100644 index 0000000000000..182cc19a97940 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/BundleProductTemplateGenerator.php @@ -0,0 +1,164 @@ +fixture = $fixture; + $this->productFactory = $productFactory; + $this->optionFactory = $optionFactory; + $this->linkFactory = $linkFactory; + } + + /** + * {@inheritdoc} + */ + public function generateEntity() + { + $product = $this->getProductTemplate( + $this->fixture['attribute_set_id'] + ); + $product->save(); + + return $product; + } + + /** + * Get product template + * + * @param int $attributeSet + * @return ProductInterface + */ + private function getProductTemplate($attributeSet) + { + $bundleOptions = $this->fixture['_bundle_options']; + $bundleProductsPerOption = $this->fixture['_bundle_products_per_option']; + $bundleVariationSkuPattern = $this->fixture['_bundle_variation_sku_pattern']; + $productRandomizerNumber = crc32(mt_rand(1, PHP_INT_MAX)); + $bundleProduct = $this->productFactory->create([ + 'data' => [ + 'attribute_set_id' => $attributeSet, + 'type_id' => Type::TYPE_BUNDLE, + 'name' => 'template name' . $productRandomizerNumber, + 'url_key' => 'template-url' . $productRandomizerNumber, + 'sku' => 'template_sku' . $productRandomizerNumber, + 'price' => 10, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'website_ids' => [1], + 'category_ids' => isset($this->fixture['category_ids']) ? [2] : null, + 'weight' => 1, + 'description' => 'description', + 'short_description' => 'short description', + 'tax_class_id' => 2, //'taxable goods', + 'price_type' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, + 'price_view' => 1, + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => 100500, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ], + 'can_save_bundle_selections' => true, + 'affect_bundle_product_selections' => true, + + ] + ]); + + $bundleProductOptions = []; + $variationN = 0; + for ($i = 1; $i <= $bundleOptions; $i++) { + $option = $this->optionFactory->create(['data' => [ + 'title' => 'Bundle Product Items ' . $i, + 'default_title' => 'Bundle Product Items ' . $i, + 'type' => 'select', + 'required' => 1, + 'delete' => '', + 'position' => $bundleOptions - $i, + 'option_id' => '', + ]]); + $option->setSku($bundleProduct->getSku()); + $option->setOptionId(null); + + $links = []; + for ($linkN = 1; $linkN <= $bundleProductsPerOption; $linkN++) { + $variationN++; + $link = $this->linkFactory->create(['data' => [ + 'sku' => sprintf($bundleVariationSkuPattern, $variationN), + 'qty' => 1, + 'can_change_qty' => 1, + 'position' => $linkN - 1, + 'price_type' => 0, + 'price' => 0.0, + 'option_id' => '', + 'is_default' => $linkN === 1, + ]]); + $links[] = $link; + } + $option->setProductLinks($links); + $bundleProductOptions[] = $option; + } + + $extension = $bundleProduct->getExtensionAttributes(); + $extension->setBundleProductOptions($bundleProductOptions); + $bundleProduct->setExtensionAttributes($extension); + // Need for set "has_options" field + $bundleProduct->setBundleOptionsData($bundleProductOptions); + $bundleSelections = array_map(function ($option) { + return array_map(function ($link) { + return $link->getData(); + }, $option->getProductLinks()); + }, $bundleProductOptions); + $bundleProduct->setBundleSelectionsData($bundleSelections); + + return $bundleProduct; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductGenerator.php new file mode 100644 index 0000000000000..800703806481e --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductGenerator.php @@ -0,0 +1,119 @@ + simple product sku pattern, which will be used as configurable variation, + * '_attributes_count' => amount of attributes on which configurable product is based, + * '_variation_count' => amount of generated variations, + * '_attributes' => product attributes on which configurable product is based , + * ] + * @see ProductGenerator + * @see ConfigurableProductTemplateGenerator + */ +class ConfigurableProductGenerator +{ + /** + * @var ProductGeneratorFactory + */ + private $productGeneratorFactory; + + /** + * @param ProductGeneratorFactory $productGeneratorFactory + */ + public function __construct(ProductGeneratorFactory $productGeneratorFactory) + { + $this->productGeneratorFactory = $productGeneratorFactory; + } + + /** + * Generate bundle products products + * + * @param int $products + * @param array $fixtureMap + * @return void + */ + public function generate($products, $fixtureMap) + { + $this->productGeneratorFactory->create([ + 'customTableMap' => [ + 'catalog_product_super_attribute_label' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['product_super_attribute_id'] = $this->generateSuperAttributeId( + $bind['product_super_attribute_id'], + $entityNumber, + $fixture + ); + } + return $binds; + }, + ], + 'catalog_product_super_link' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['parent_id'] = $productId; + $bind['product_id'] = $this->generateSimpleProductId( + $bind['product_id'], + $entityNumber, + $fixture + ); + } + return $binds; + }, + ], + 'catalog_product_relation' => [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $bind['parent_id'] = $productId; + $bind['child_id'] = $this->generateSimpleProductId( + $bind['child_id'], + $entityNumber, + $fixture + ); + } + return $binds; + }, + ], + ] + ])->generate($products, $fixtureMap); + } + + /** + * Generate value of option_id for $entityNumber bundle product based on previous option_id + * + * @param int $superAttributeId + * @param int $entityNumber + * @param array $fixture + * @return int + */ + private function generateSuperAttributeId($superAttributeId, $entityNumber, array $fixture) + { + return $superAttributeId + $entityNumber * $fixture['_attributes_count'] + $fixture['_attributes_count']; + } + + /** + * Generate value of simple product id which is used for $entityNumber bundle product as option item + * + * @param int $previousProductId + * @param int $entityNumber + * @param array $fixture + * @return mixed + */ + private function generateSimpleProductId($previousProductId, $entityNumber, array $fixture) + { + return $previousProductId + $entityNumber * $fixture['_variation_count']; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductTemplateGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductTemplateGenerator.php new file mode 100644 index 0000000000000..71916c611e1fa --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ConfigurableProductTemplateGenerator.php @@ -0,0 +1,164 @@ +fixture = $fixture; + $this->productFactory = $productFactory; + $this->optionFactory = $optionFactory; + $this->resourceConnection = $resourceConnection; + } + + /** + * {@inheritdoc} + */ + public function generateEntity() + { + $attributeSet = $this->fixture['attribute_set_id']; + $product = $this->getProductTemplate($attributeSet); + + $product->save(); + + return $product; + } + + /** + * Get product template + * + * @param int $attributeSet + * @return ProductInterface + */ + private function getProductTemplate($attributeSet) + { + $productRandomizerNumber = crc32(mt_rand(1, PHP_INT_MAX)); + $product = $this->productFactory->create([ + 'data' => [ + 'attribute_set_id' => $attributeSet, + 'type_id' => Configurable::TYPE_CODE, + 'name' => 'template name' . $productRandomizerNumber, + 'url_key' => 'template-url' . $productRandomizerNumber, + 'sku' => 'template_sku' . $productRandomizerNumber, + 'meta_description' => 'Configurable Product', + 'meta_keyword' => $productRandomizerNumber, + 'meta_title' => $productRandomizerNumber, + 'price' => 10, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'website_ids' => (array)$this->fixture['website_ids'](1, 0), + 'category_ids' => isset($this->fixture['category_ids']) ? [2] : null, + 'weight' => 1, + 'description' => 'description', + 'short_description' => 'short description', + 'tax_class_id' => 2, //'taxable goods', + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => 100500, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ], + // Need for set "has_options" field + 'can_save_configurable_attributes' => true, + 'configurable_attributes_data' => $this->fixture['_attributes'], + ] + ]); + + $attributes = []; + foreach ($this->fixture['_attributes'] as $index => $attribute) { + $attributeValues = []; + foreach ($attribute['values'] as $value) { + $attributeValues[] = [ + 'label' => $attribute['name'], + 'attribute_id' => $attribute['id'], + 'value_index' => $value + ]; + } + $attributes[] = [ + 'attribute_id' => $attribute['id'], + 'code' => $attribute['name'], + 'label' => $attribute['name'], + 'position' => $index, + 'values' => $attributeValues, + ]; + } + $configurableOptions = $this->optionFactory->create($attributes); + $extensionConfigurableAttributes = $product->getExtensionAttributes(); + $extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); + $extensionConfigurableAttributes->setConfigurableProductLinks($this->getAssociatedProductIds()); + $product->setExtensionAttributes($extensionConfigurableAttributes); + + return $product; + } + + /** + * Get configurable variation ids. Retrieve first simple product id by sku pattern from DB and generate next values + * for all variations + * + * @return array + */ + private function getAssociatedProductIds() + { + $associatedProductIds = []; + $connection = $this->resourceConnection->getConnection(); + $firstSimpleProductId = $connection->fetchRow( + $connection->select() + ->from($this->resourceConnection->getTableName('catalog_product_entity')) + ->where('sku = ?', $this->fixture['_variation_sku_pattern']) + )['entity_id']; + + for ($i = 0; $i < $this->fixture['_variation_count']; $i++) { + $associatedProductIds[] = $firstSimpleProductId + $i; + } + + return $associatedProductIds; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerGenerator.php new file mode 100644 index 0000000000000..7b66e26748667 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerGenerator.php @@ -0,0 +1,158 @@ +entityGeneratorFactory = $entityGeneratorFactory; + $this->customerTemplateGenerator = $customerTemplateGenerator; + $this->resourceConnection = $resourceConnection; + } + + /** + * Generate entities + * + * @param int $customers + * @param array $fixtureMap + * @return void + */ + public function generate($customers, array $fixtureMap) + { + $this->entityGeneratorFactory + ->create([ + 'entityType' => CustomerInterface::class, + 'customTableMap' => [ + 'customer_entity' => [ + 'handler' => $this->getCustomerEntityHandler() + ], + + 'customer_address_entity' => [ + 'handler' => $this->getCustomerAddressEntityHandler() + ] + ], + ])->generate( + $this->customerTemplateGenerator, + $customers, + function ($customerId) use ($fixtureMap) { + $fixtureMap['customer_data'] = call_user_func($fixtureMap['customer_data'], $customerId); + return $fixtureMap; + } + ); + + $this->addDefaultAddresses(); + } + + /** + * Creates closure that is used + * to replace default customer data with data from fixture + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @return \Closure + */ + private function getCustomerEntityHandler() + { + return function ($entityId, $entityNumber, $fixtureMap, $binds) { + return array_map( + 'array_merge', + $binds, + array_fill(0, count($binds), $fixtureMap['customer_data']['customer']) + ); + }; + } + + /** + * Creates closure that is used + * to replace default customer address data with data from fixture + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @return \Closure + */ + private function getCustomerAddressEntityHandler() + { + return function ($entityId, $entityNumber, $fixtureMap, $binds) { + return array_map( + 'array_merge', + array_fill(0, count($fixtureMap['customer_data']['addresses']), reset($binds)), + $fixtureMap['customer_data']['addresses'] + ); + }; + } + + /** + * Set default billing and shipping addresses for customer + * + * @return void + */ + private function addDefaultAddresses() + { + $this->getConnection()->query( + sprintf( + ' + update `%s` customer + join ( + select + parent_id, min(entity_id) as min, max(entity_id) as max + from `%s` + group by parent_id + ) customer_address on customer_address.parent_id = customer.entity_id + set + customer.default_billing = customer_address.min, + customer.default_shipping = customer_address.max + ', + $this->resourceConnection->getTableName('customer_entity'), + $this->resourceConnection->getTableName('customer_address_entity') + ) + ); + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (null === $this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + + return $this->connection; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerTemplateGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerTemplateGenerator.php new file mode 100644 index 0000000000000..d324deb28ea28 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/CustomerTemplateGenerator.php @@ -0,0 +1,132 @@ +customerFactory = $customerFactory; + $this->addressFactory = $addressFactory; + $this->storeManager = $storeManager; + } + + /** + * {@inheritdoc} + */ + public function generateEntity() + { + $customer = $this->getCustomerTemplate(); + $customer->save(); + $address = $this->getAddressTemplate($customer->getId()); + $address->save(); + + return $customer; + } + + /** + * Get customer template + * + * @return Customer + */ + private function getCustomerTemplate() + { + $customerRandomizerNumber = crc32(mt_rand(1, PHP_INT_MAX)); + + $now = new \DateTime(); + + return $this->customerFactory->create([ + 'data' => [ + 'email' => sprintf('user_%s@example.com', $customerRandomizerNumber), + 'confirmation' => null, + 'created_at' => $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), + 'created_in' => 'Default', + 'default_billing' => '1', + 'default_shipping' => '1', + 'disable_auto_group_change' => '0', + 'dob' => '12-10-1991', + 'firstname' => 'Firstname', + 'gender' => 1, + 'group_id' => '1', + 'lastname' => 'Lastname', + 'middlename' => '', + 'password_hash' => '', + 'prefix' => null, + 'rp_token' => null, + 'rp_token_created_at' => null, + 'store_id' => $this->storeManager->getDefaultStoreView()->getId(), + 'suffix' => null, + 'taxvat' => null, + 'website_id' => $this->storeManager->getDefaultStoreView()->getWebsiteId(), + 'password' => '123123q', + ] + ]); + } + + /** + * @param int $customerId + * @return Address + */ + private function getAddressTemplate($customerId) + { + return $this->addressFactory->create([ + 'data' => [ + 'parent_id' => $customerId, + 'attribute_set_id' => 2, + 'telephone' => 3468676, + 'postcode' => 75477, + 'country_id' => 'US', + 'city' => 'CityM', + 'company' => 'CompanyName', + 'street' => 'Green str, 67', + 'lastname' => 'Smith', + 'firstname' => 'John', + 'region_id' => 1, + 'fax' => '04040404', + 'middlename' => '', + 'prefix' => '', + 'region' => 'Arkansas', + 'suffix' => '', + 'vat_id' => '', + 'default_billing_' => '1', + 'default_shipping_' => '1', + ] + ]); + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/EntityGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/EntityGenerator.php new file mode 100644 index 0000000000000..ebc121b8c06f2 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/EntityGenerator.php @@ -0,0 +1,471 @@ + entity if field name which linked to entity table primary key + * or SKIP_ENTITY_ID_BINDING for do not set entity_id during generation + * 'handler' => function($entityId, $fixture, $binds) callback for process binding for custom table + * 'fields' => [key name in fixture for process custom bindings, ...] + * ] + */ + private $customTableMap; + + /** + * entity table class name + * + * @var string + */ + private $entityType; + + /** + * @var \Magento\Setup\Model\FixtureGenerator\SqlCollector + */ + private $sqlCollector; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resourceConnection; + + /** + * @var \Magento\Eav\Model\ResourceModel\AttributeLoader + */ + private $attributeLoader; + + /** + * @var \Magento\Eav\Api\Data\AttributeInterface[] + */ + private $attributes; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var array + */ + private $tableToEntityIdMap; + + /** + * @var string + */ + private $entityTable; + + /** + * List of tables where entity id information is stored + * + * @var array + */ + private $primaryEntityIdTables; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + + /** + * @var \Magento\Framework\EntityManager\EntityMetadataInterface + */ + private $entityMetadata; + + /** + * @var \Magento\Framework\EntityManager\Sequence\SequenceRegistry + */ + private $sequenceRegistry; + + /** + * @var bool + */ + private $isMappingInitialized = false; + + /** + * @var int + */ + private $bunchSize; + + /** + * @param SqlCollector $sqlCollector + * @param \Magento\Eav\Model\ResourceModel\AttributeLoader $attributeLoader + * @param \Magento\Framework\App\ResourceConnection $resourceConnection + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param \Magento\Framework\EntityManager\Sequence\SequenceRegistry $sequenceRegistry + * @param string $entityType + * @param array $customTableMap + * @param int $bunchSize + */ + public function __construct( + \Magento\Setup\Model\FixtureGenerator\SqlCollector $sqlCollector, + \Magento\Eav\Model\ResourceModel\AttributeLoader $attributeLoader, + \Magento\Framework\App\ResourceConnection $resourceConnection, + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + \Magento\Framework\EntityManager\Sequence\SequenceRegistry $sequenceRegistry, + $entityType, + $customTableMap = [], + $bunchSize = self::SQL_DEFAULT_BUNCH_AMOUNT + ) { + $this->sqlCollector = $sqlCollector; + $this->resourceConnection = $resourceConnection; + $this->attributeLoader = $attributeLoader; + $this->metadataPool = $metadataPool; + $this->sequenceRegistry = $sequenceRegistry; + $this->customTableMap = $customTableMap; + $this->entityType = $entityType; + $this->bunchSize = (int)$bunchSize; + } + + /** + * Generate entities + * + * @param TemplateEntityGeneratorInterface $entityGenerator + * @param int $entitiesAmount + * @param callable $fixture + * @return void + */ + public function generate(TemplateEntityGeneratorInterface $entityGenerator, $entitiesAmount, callable $fixture) + { + $this->sqlCollector->enable(); + $entity = $entityGenerator->generateEntity(); + $this->sqlCollector->disable(); + $entity->delete(); + + $map = []; + $processed = 0; + $entitiesAmount = (int)$entitiesAmount; + gc_disable(); + for ($entityNumber = 0; $entityNumber < $entitiesAmount; $entityNumber++) { + $processed++; + $map = array_merge_recursive($map, $this->getSqlQueries($entity, $entityNumber, $fixture)); + + if ($processed % $this->bunchSize === 0 || $entityNumber === ($entitiesAmount - 1)) { + $this->saveEntities($map); + } + } + gc_enable(); + } + + /** + * Provide list of sql queries for create a new entity + * + * @param object $entity + * @param int $entityNumber + * @param callable $fixtureMap + * @return array + */ + private function getSqlQueries($entity, $entityNumber, callable $fixtureMap) + { + $metadata = $this->getEntityMetadata(); + $this->initializeMapping(); + + $entityId = $entity->getData($metadata->getIdentifierField()) + $entityNumber; + $entityLinkId = $entity->getData($metadata->getLinkField()) + $entityNumber; + $fixtureMap = $fixtureMap($entityId, $entityNumber); + + $sql = []; + foreach ($this->sqlCollector->getSql() as $pattern) { + list ($binds, $table) = $pattern; + + if (!isset($sql[$table])) { + $sql[$table] = []; + } + + foreach ($binds as &$bind) { + if ($table === $this->getEntityTable()) { + $bind[$metadata->getLinkField()] = $entityLinkId; + $bind[$metadata->getIdentifierField()] = $entityId; + } + + if ($bind) { + $this->setNewBindValue($entityId, $entityNumber, $table, $bind, $fixtureMap); + } + if (self::SKIP_ENTITY_ID_BINDING === $this->getEntityIdField($table)) { + continue; + } + if ($this->getEntityIdField($table) === $metadata->getLinkField()) { + $bind[$this->getEntityIdField($table)] = $entityLinkId; + } else { + $bind[$this->getEntityIdField($table)] = $entityId; + } + } + + $binds = $this->bindWithCustomHandler($table, $entityId, $entityNumber, $fixtureMap, $binds); + $sql[$table] = array_merge($sql[$table], $binds); + } + + return $sql; + } + + /** + * If custom handler passed for table then override binds with it + * + * @param string $table + * @param int $entityId + * @param int $entityNumber + * @param array $fixtureMap + * @param array $binds + * @return array + */ + private function bindWithCustomHandler($table, $entityId, $entityNumber, $fixtureMap, $binds) + { + if (isset($this->customTableMap[$table]['handler']) + && is_callable($this->customTableMap[$table]['handler']) + ) { + $binds = $this->customTableMap[$table]['handler']($entityId, $entityNumber, $fixtureMap, $binds); + } + + return $binds; + } + + /** + * Save entities to DB and reset entities holder + * + * @param array $map + * @return void + */ + private function saveEntities(array &$map) + { + foreach ($map as $table => $data) { + $this->getConnection()->insertMultiple($table, $data); + } + + $map = []; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (null === $this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + + return $this->connection; + } + + /** + * @return \Magento\Framework\EntityManager\EntityMetadataInterface + */ + private function getEntityMetadata() + { + if (null === $this->entityMetadata) { + $this->entityMetadata = $this->metadataPool->getMetadata($this->entityType); + } + + return $this->entityMetadata; + } + + /** + * Get entity table name + * + * @return string + */ + private function getEntityTable() + { + if (null === $this->entityTable) { + $this->entityTable = $this->getEntityMetadata()->getEntityTable(); + } + + return $this->entityTable; + } + + /** + * Get field name for specific table where stored link to primary key of entity table + * Find field by FK to entity table + * + * @param string $table + * @return string + * @throws ValidatorException + */ + private function getEntityIdField($table) + { + if (!isset($this->tableToEntityIdMap[$table])) { + $foreignKey = null; + foreach ($this->primaryEntityIdTables as $primaryTable) { + $foreignKey = array_filter( + $this->getConnection()->getForeignKeys($table), + function ($ddl) use ($primaryTable) { + return $ddl['REF_TABLE_NAME'] === $primaryTable + && $ddl['REF_COLUMN_NAME'] === $this->getEntityIdField($primaryTable); + } + ); + if ($foreignKey) { + break; + } + } + if (!$foreignKey) { + throw new ValidatorException(__('Cannot find entity id field for table "%1"', $table)); + } + $this->tableToEntityIdMap[$table] = current($foreignKey)['COLUMN_NAME']; + } + + return $this->tableToEntityIdMap[$table]; + } + + /** + * Initialize map between table and entity id and convert table name to valid table name + * + * @return void + * @throws ValidatorException + */ + private function initializeMapping() + { + if (!$this->isMappingInitialized) { + $this->isMappingInitialized = true; + + $this->initCustomTables(); + + $this->primaryEntityIdTables = [ + $this->getEntityMetadata()->getEntityTable() + ]; + $entitySequence = $this->sequenceRegistry->retrieve($this->entityType); + if (isset($entitySequence['sequenceTable'])) { + $this->primaryEntityIdTables[] = $this->resourceConnection->getTableName( + $entitySequence['sequenceTable'] + ); + } + + foreach ($this->primaryEntityIdTables as $table) { + $ddl = array_filter( + $this->getConnection()->describeTable($table), + function ($data) { + return $data['PRIMARY'] === true; + } + ); + if (!$ddl) { + throw new ValidatorException(__('Cannot find primary key for table "%1"', $table)); + } + $this->tableToEntityIdMap[$table] = current($ddl)['COLUMN_NAME']; + } + } + } + + /** + * Rebind table name with real name, initialize table map for tables without foreign key to entity table + * + * @return void + */ + private function initCustomTables() + { + $customTableData = [ + 'entity_id_field' => null, + 'handler' => null, + 'fields' => [], + ]; + $customTableMap = []; + foreach ($this->customTableMap as $table => $data) { + $table = $this->resourceConnection->getTableName($table); + $data = array_merge($customTableData, $data); + $customTableMap[$table] = $data; + if ($data['entity_id_field']) { + $this->tableToEntityIdMap[$table] = $data['entity_id_field']; + } + } + $this->customTableMap = $customTableMap; + } + + /** + * Get EAV attributes metadata for non-static attributes + * + * @return array + */ + private function getAttributesMetadata() + { + if (null === $this->attributes) { + foreach ($this->attributeLoader->getAttributes($this->entityType) as $attribute) { + if ($attribute->isStatic()) { + continue; + } + $this->attributes[$attribute->getBackendTable()][$attribute->getAttributeCode()] = [ + 'value_field' => 'value', + 'link_field' => 'attribute_id', + 'attribute_id' => $attribute->getAttributeId(), + ]; + } + } + + return $this->attributes; + } + + /** + * Set new bind value for new record + * + * @param int $entityId + * @param int $entityNumber + * @param string $table + * @param array $bind + * @param array $fixtureMap + * + * @return void + */ + private function setNewBindValue($entityId, $entityNumber, $table, array &$bind, array $fixtureMap) + { + $attributes = $this->getAttributesMetadata(); + if (isset($attributes[$table])) { + // Process binding new value for eav attributes + foreach ($fixtureMap as $fixtureField => $fixture) { + if (!isset($attributes[$table][$fixtureField])) { + continue; + } + $attribute = $attributes[$table][$fixtureField]; + + if (isset($bind[$attribute['link_field']]) + && $bind[$attribute['link_field']] === $attribute[$attribute['link_field']] + ) { + $bind[$attribute['value_field']] = $this->getBindValue($fixture, $entityId, $entityNumber); + break; + } + } + } elseif (isset($this->customTableMap[$table])) { + foreach ($this->customTableMap[$table]['fields'] as $field => $fixtureField) { + $bind[$field] = $this->getFixtureValue($fixtureField, $entityId, $entityNumber, $fixtureMap); + } + } + } + + /** + * @param string $fixtureField + * @param int $entityId + * @param int $entityNumber + * @param array $fixtureMap + * @return mixed|string + */ + private function getFixtureValue($fixtureField, $entityId, $entityNumber, array $fixtureMap) + { + $fixture = isset($fixtureMap[$fixtureField]) ? $fixtureMap[$fixtureField] : null; + return $fixture ? $this->getBindValue($fixture, $entityId, $entityNumber) : ''; + } + + /** + * @param callable|mixed $fixture + * @param int $entityId + * @param int $entityNumber + * @return string + */ + private function getBindValue($fixture, $entityId, $entityNumber) + { + $bindValue = is_callable($fixture) + ? call_user_func($fixture, $entityId, $entityNumber) + : $fixture; + + return is_array($bindValue) ? array_shift($bindValue) : $bindValue; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php new file mode 100644 index 0000000000000..8f5b4b52f66be --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php @@ -0,0 +1,335 @@ + function ($entityId, $entityIndex) {return 'Product Name' . $entityIndex}, + * 'sku' => function ($entityId, $entityIndex) {return 'Product Sku' . $entityIndex}, + * ] + * And optional parameters (by default will be populated with default values) + * [ + * 'attribute_set_id' => value or callback in format function ($entityId, $entityIndex) {return attribute_id} + * 'additional_attributes' => callback in format function ($entityId, $entityIndex) {return [attribute => value]} + * 'url_key' => callback in format function ($entityId, $entityIndex) {return url_key} + * 'website_ids' => callback in format function ($entityId, $entityIndex) {return [website_id]} + * 'status' => value or callback in format function ($entityId, $entityIndex) {return status} + * 'price' => value or callback in format function ($entityId, $entityIndex) {return price} + * 'description' => value or callback in format function ($entityId, $entityIndex) {return description} + * 'short_description' => value or callback in format function ($entityId, $entityIndex) {return short_description} + * 'category_ids' => callback in format function ($entityId, $entityIndex) {return category_ids} + * 'type_id' => value or callback in format function ($entityId, $entityIndex) {return type_id} + * 'meta_keyword' => value or callback in format function ($entityId, $entityIndex) {return meta_keyword} + * 'meta_title' => value or callback in format function ($entityId, $entityIndex) {return meta_title} + * ] + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ProductGenerator +{ + /** + * @var \Magento\Catalog\Model\ProductFactory + */ + private $productFactory; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory + */ + private $categoryCollectionFactory; + + /** + * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory + */ + private $urlRewriteFactory; + + /** + * @var \Magento\Store\Model\ResourceModel\Store\CollectionFactory + */ + private $storeCollectionFactory; + + /** + * @var array + */ + private $categories = []; + + /** + * @var array + */ + private $storesPerWebsite = []; + + /** + * @var EntityGeneratorFactory + */ + private $entityGeneratorFactory; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var ProductTemplateGeneratorFactory + */ + private $productTemplateGeneratorFactory; + + /** + * @var array + */ + private $productUrlSuffix = []; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var array + */ + private $customTableMap; + + /** + * @param \Magento\Catalog\Model\ProductFactory $productFactory + * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory + * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory + * @param \Magento\Store\Model\ResourceModel\Store\CollectionFactory $storeCollectionFactory + * @param EntityGeneratorFactory $entityGeneratorFactory + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param ProductTemplateGeneratorFactory $productTemplateGeneratorFactory + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param array $customTableMap + */ + public function __construct( + \Magento\Catalog\Model\ProductFactory $productFactory, + \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory, + \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory, + \Magento\Store\Model\ResourceModel\Store\CollectionFactory $storeCollectionFactory, + EntityGeneratorFactory $entityGeneratorFactory, + \Magento\Store\Model\StoreManagerInterface $storeManager, + ProductTemplateGeneratorFactory $productTemplateGeneratorFactory, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + $customTableMap = [] + ) { + $this->productFactory = $productFactory; + $this->categoryCollectionFactory = $categoryCollectionFactory; + $this->urlRewriteFactory = $urlRewriteFactory; + $this->storeCollectionFactory = $storeCollectionFactory; + $this->entityGeneratorFactory = $entityGeneratorFactory; + $this->storeManager = $storeManager; + $this->productTemplateGeneratorFactory = $productTemplateGeneratorFactory; + $this->scopeConfig = $scopeConfig; + $this->customTableMap = $customTableMap; + } + + /** + * Generate simple products + * + * @param int $products + * @param array $fixtureMap + * @return void + */ + public function generate($products, $fixtureMap) + { + $this->initializeFixtureDefaultValues($fixtureMap); + $attributeSets = []; + // prepare attribute sets distribution for save products per attribute set + for ($productNumber = 1; $productNumber <= $products; $productNumber++) { + $attributeSetId = $this->getFixtureValue('attribute_set_id', $productNumber, $productNumber, $fixtureMap); + if (!isset($attributeSets[$attributeSetId])) { + $attributeSets[$attributeSetId] = 0; + } + $attributeSets[$attributeSetId]++; + } + + $customTableMap = [ + 'url_rewrite' => [ + 'entity_id_field' => 'entity_id', + 'handler' => function ($productId, $entityNumber, $fixture) { + return $this->urlRewriteHandler($productId, $entityNumber, $fixture); + }, + ], + 'catalog_category_product' => [ + 'fields' => [ + 'category_id' => 'category_ids', + ], + ], + 'catalog_product_entity' => [ + 'fields' => [ + 'attribute_set_id' => 'attribute_set_id', + 'sku' => 'sku', + ], + ], + ]; + if (count($fixtureMap['website_ids'](1, 0)) === 1) { + // Get website id from fixture in case when one site is assigned per product + $customTableMap['catalog_product_website'] = [ + 'fields' => [ + 'website_id' => 'website_ids', + ] + ]; + } + $generator = $this->entityGeneratorFactory->create( + [ + 'entityType' => ProductInterface::class, + 'customTableMap' => array_merge($customTableMap, $this->customTableMap) + ] + ); + foreach ($attributeSets as $attributeSetId => $productsAmount) { + $fixtureMap = array_merge($fixtureMap, ['attribute_set_id' => $attributeSetId]); + $generator->generate( + $this->productTemplateGeneratorFactory->create($fixtureMap), + $productsAmount, + function ($productNumber, $entityNumber) use ($attributeSetId, $fixtureMap) { + // add additional attributes to fixture for fulfill it during product generation + return array_merge( + $fixtureMap, + $fixtureMap['additional_attributes']($attributeSetId, $productNumber, $entityNumber) + ); + } + ); + } + } + + /** + * @param array $fixture + * @return void + */ + private function initializeFixtureDefaultValues(array &$fixture) + { + $defaultValues = [ + 'attribute_set_id' => function () { + return $this->productFactory->create()->getDefaultAttributeSetId(); + }, + 'additional_attributes' => function () { + return []; + }, + 'url_key' => function ($productId, $entityNumber) use ($fixture) { + return strtolower(str_replace(' ', '-', $fixture['sku']($productId, $entityNumber))); + }, + 'website_ids' => function () { + return $this->storeManager->getDefaultStoreView()->getWebsiteId(); + }, + 'status' => Status::STATUS_ENABLED, + ]; + foreach ($defaultValues as $fixtureKey => $value) { + if (!isset($fixture[$fixtureKey])) { + $fixture[$fixtureKey] = $value; + } + } + } + + /** + * @param string $fixtureKey + * @param int $productId + * @param int $entityNumber + * @param array $fixtureMap + * @return mixed|string + */ + private function getFixtureValue($fixtureKey, $productId, $entityNumber, $fixtureMap) + { + $fixtureValue = isset($fixtureMap[$fixtureKey]) ? $fixtureMap[$fixtureKey] : null; + return $fixtureValue ? $this->getBindValue($fixtureValue, $productId, $entityNumber) : ''; + } + + /** + * @param callable|mixed $fixtureValue + * @param int $productId + * @param int $entityNumber + * @return mixed + */ + private function getBindValue($fixtureValue, $productId, $entityNumber) + { + return is_callable($fixtureValue) + ? call_user_func($fixtureValue, $productId, $entityNumber) + : $fixtureValue; + } + + /** + * Handle generation sql query for url rewrite + * + * @param int $productId + * @param int $entityNumber + * @param array $fixtureMap + * @return array + */ + private function urlRewriteHandler($productId, $entityNumber, $fixtureMap) + { + $binds = []; + $websiteIds = $fixtureMap['website_ids']($productId, $entityNumber); + $websiteIds = is_array($websiteIds) ? $websiteIds : [$websiteIds]; + + $bindPerStore = []; + $requestPath = $this->getFixtureValue('url_key', $productId, $entityNumber, $fixtureMap); + $targetPath = 'catalog/product/view/id/' . $productId; + $urlRewrite = $this->urlRewriteFactory + ->create() + ->setRequestPath($requestPath) + ->setTargetPath($targetPath) + ->setEntityId($productId) + ->setEntityType('product'); + $binds[] = $urlRewrite->toArray(); + + if (isset($fixtureMap['category_ids'])) { + $categoryId = $fixtureMap['category_ids']($productId, $entityNumber); + if (!isset($this->categories[$categoryId])) { + $this->categories[$categoryId] = $this->categoryCollectionFactory + ->create() + ->addIdFilter($categoryId) + ->addAttributeToSelect('url_path') + ->getFirstItem() + ->getUrlPath(); + } + $urlRewrite->setMetadata(['category_id' => $categoryId]) + ->setRequestPath($this->categories[$categoryId] . '/' . $requestPath) + ->setTargetPath($targetPath . '/category/' . $categoryId); + $binds[] = $urlRewrite->toArray(); + } + + foreach ($websiteIds as $websiteId) { + if (!isset($this->storesPerWebsite[$websiteId])) { + $this->storesPerWebsite[$websiteId] = $this->storeCollectionFactory + ->create() + ->addWebsiteFilter($websiteId) + ->getAllIds(); + } + foreach ($binds as $bind) { + foreach ($this->storesPerWebsite[$websiteId] as $storeId) { + $bindWithStore = $bind; + $bindWithStore['store_id'] = $storeId; + $bindWithStore['request_path'] .= $this->getUrlSuffix($storeId); + $bindPerStore[] = $bindWithStore; + } + } + } + + return $bindPerStore; + } + + /** + * Get url suffix per store for product + * + * @param int $storeId + * @return string + */ + private function getUrlSuffix($storeId) + { + if (!isset($this->productUrlSuffix[$storeId])) { + $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( + ProductUrlPathGenerator::XML_PATH_PRODUCT_URL_SUFFIX, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + return $this->productUrlSuffix[$storeId]; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductTemplateGeneratorFactory.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductTemplateGeneratorFactory.php new file mode 100644 index 0000000000000..e364faaa08cc7 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductTemplateGeneratorFactory.php @@ -0,0 +1,58 @@ + SimpleProductTemplateGenerator::class, + BundleType::TYPE_CODE => BundleProductTemplateGenerator::class, + Configurable::TYPE_CODE => ConfigurableProductTemplateGenerator::class, + ]; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @param array $fixture + * @return TemplateEntityGeneratorInterface + * @throws \InvalidArgumentException + */ + public function create(array $fixture) + { + $type = isset($fixture['type_id']) ? $fixture['type_id'] : Type::TYPE_SIMPLE; + if (!isset($this->templateEntityMap[$type])) { + throw new \InvalidArgumentException(sprintf( + 'Cannot instantiate product template generator. Wrong type_id "%s" passed', + $type + )); + } + + return $this->objectManager->create($this->templateEntityMap[$type], ['fixture' => $fixture]); + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/SimpleProductTemplateGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/SimpleProductTemplateGenerator.php new file mode 100644 index 0000000000000..5024b05b6e82b --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/SimpleProductTemplateGenerator.php @@ -0,0 +1,97 @@ +fixture = $fixture; + $this->productFactory = $productFactory; + } + + /** + * {@inheritdoc} + */ + public function generateEntity() + { + $attributeSet = $this->fixture['attribute_set_id']; + $product = $this->getProductTemplate( + $attributeSet, + $this->fixture['additional_attributes']($attributeSet, 0, 0) + ); + $product->save(); + + return $product; + } + + /** + * Get product template + * + * @param int $attributeSet + * @param array $additionalAttributes + * @return ProductInterface + */ + private function getProductTemplate($attributeSet, $additionalAttributes = []) + { + $productRandomizerNumber = crc32(mt_rand(1, PHP_INT_MAX)); + $product = $this->productFactory->create([ + 'data' => [ + 'attribute_set_id' => $attributeSet, + 'type_id' => Type::TYPE_SIMPLE, + 'name' => 'template name' . $productRandomizerNumber, + 'url_key' => 'template-url' . $productRandomizerNumber, + 'sku' => 'template_sku' . $productRandomizerNumber, + 'price' => 10, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'website_ids' => (array)$this->fixture['website_ids'](1, 0), + 'category_ids' => isset($this->fixture['category_ids']) ? [2] : null, + 'weight' => 1, + 'description' => 'description', + 'short_description' => 'short description', + 'tax_class_id' => 2, //'taxable goods', + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => 100500, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ], + ] + ]); + + foreach ($additionalAttributes as $attributeCode => $attributeValue) { + $product->setData($attributeCode, $attributeValue); + } + + return $product; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php b/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php new file mode 100644 index 0000000000000..c94a89bede00f --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php @@ -0,0 +1,141 @@ +resourceConnection = $resourceConnection; + } + + /** + * @param string $sql + * @param array $bind + * @return void + */ + private function addSql($sql, $bind) + { + preg_match('~INSERT INTO `(.*)` \((.*)\) VALUES (\(.*\))+~', $sql, $queryMatches); + if ($queryMatches) { + $table = $queryMatches[1]; + $fields = preg_replace('~[\s+`]+~', '', $queryMatches[2]); + $fields = $fields ? explode(',', $fields) : []; + $sqlBindGroupAmount = count(explode('), (', $queryMatches[3])); + preg_match(' ~\((.*?)\)~', $queryMatches[3], $sqlBind); + $sqlBind = preg_replace(['~,\s*~', '~\'~'], [',', ''], $sqlBind[1]); + $sqlBind = $sqlBind ? explode(',', $sqlBind) : []; + $binds = []; + + // process multi queries + if ($sqlBindGroupAmount > 1) { + $valuesCount = count($bind)/$sqlBindGroupAmount; + for ($i = 0; $i < $sqlBindGroupAmount; $i++) { + $binds[] = array_combine( + $fields, + $this->handleBindValues($sqlBind, $bind, $i * $valuesCount) + ); + } + } else { + $sqlBind = $this->handleBindValues($sqlBind, $bind); + $binds[] = array_combine($fields, $sqlBind); + } + $this->sql[] = [$binds, $table]; + } + } + + /** + * @param array $sqlBind + * @param array $bind + * @param int $bindPosition + * @return array + */ + private function handleBindValues(array $sqlBind, array $bind, $bindPosition = 0) + { + $bind = array_values($bind); + foreach ($sqlBind as $i => $fieldValue) { + if ($fieldValue === '?') { + $sqlBind[$i] = $bind[$bindPosition]; + $bindPosition++; + } + } + + return $sqlBind; + } + + /** + * @return array + */ + public function getSql() + { + return $this->sql; + } + + /** + * Enable sql parsing + * + * @return void + */ + public function enable() + { + $this->sql = []; + $this->getProfiler()->clear(); + $this->getProfiler()->setEnabled(true); + } + + /** + * Disable sql parsing and collect all queries from profiler + * + * @return void + */ + public function disable() + { + $this->getProfiler()->setEnabled(false); + $queries = $this->getProfiler()->getQueryProfiles() ?: []; + foreach ($queries as $query) { + if ($query->getQueryType() === Profiler::INSERT) { + $this->addSql($query->getQuery(), $query->getQueryParams()); + } + } + } + + /** + * @return \Zend_Db_Profiler + */ + private function getProfiler() + { + if ($this->profiler === null) { + $this->profiler = $this->resourceConnection->getConnection()->getProfiler(); + } + + return $this->profiler; + } +} diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/TemplateEntityGeneratorInterface.php b/setup/src/Magento/Setup/Model/FixtureGenerator/TemplateEntityGeneratorInterface.php new file mode 100644 index 0000000000000..7d36948830756 --- /dev/null +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/TemplateEntityGeneratorInterface.php @@ -0,0 +1,18 @@ + self::COMPOSER_SHOW, - self::PARAM_PACKAGE => $package, - self::PARAM_AVAILABLE => true - ]; - - $applicationFactory = $this->objectManagerProvider->get() - ->get(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class); - /** @var \Magento\Composer\MagentoComposerApplication $application */ - $application = $applicationFactory->create(); - - $result = $application->runComposerCommand($commandParams); - $matches = []; - preg_match($versionsPattern, $result, $matches); - if (isset($matches[1])) { - return explode(', ', $matches[1]); - } + } + + return $this->getAvailableVersionsFromAllRepositories($package); + } + + /** + * Get available versions of package by "composer show" command + * + * @param string $package + * @return array + * @exception \RuntimeException + */ + private function getAvailableVersionsFromAllRepositories($package) + { + $versionsPattern = '/^versions\s*\:\s(.+)$/m'; + + $commandParams = [ + self::PARAM_COMMAND => self::COMPOSER_SHOW, + self::PARAM_PACKAGE => $package, + self::PARAM_AVAILABLE => true + ]; + + $applicationFactory = $this->objectManagerProvider->get() + ->get(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class); + /** @var \Magento\Composer\MagentoComposerApplication $application */ + $application = $applicationFactory->create(); + + $result = $application->runComposerCommand($commandParams); + $matches = []; + preg_match($versionsPattern, $result, $matches); + if (isset($matches[1])) { + return explode(', ', $matches[1]); } throw new \RuntimeException( diff --git a/setup/src/Magento/Setup/Model/PayloadValidator.php b/setup/src/Magento/Setup/Model/PayloadValidator.php index b024b028cb81f..6cab630ca7716 100644 --- a/setup/src/Magento/Setup/Model/PayloadValidator.php +++ b/setup/src/Magento/Setup/Model/PayloadValidator.php @@ -1,6 +1,6 @@ current) { - $this->current = array_map('strtolower', get_loaded_extensions()); + $this->current = array_map(function ($ext) { + return str_replace(' ', '-', strtolower($ext)); + }, get_loaded_extensions()); } return $this->current; } diff --git a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php index 74b2afd1419c4..c593059edd4a6 100644 --- a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php +++ b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php @@ -1,6 +1,6 @@ descriptionGenerator = $descriptionGenerator; + $this->searchTermManager = $searchTermManager; + } + + /** + * Generate description with search terms + * + * @param int $currentProductIndex + * @return string + */ + public function generate($currentProductIndex) + { + $description = $this->getDescription(); + $this->searchTermManager->applySearchTermsToDescription($description, (int) $currentProductIndex); + + return $description; + } + + /** + * Generate new description or use cached one + * + * @param bool $useCachedDescription + * @return string + */ + private function getDescription($useCachedDescription = true) + { + if ($useCachedDescription !== true || $this->cachedDescription === null) { + $this->cachedDescription = $this->descriptionGenerator->generate(); + } + + return $this->cachedDescription; + } +} diff --git a/setup/src/Magento/Setup/Model/SearchTermDescriptionGeneratorFactory.php b/setup/src/Magento/Setup/Model/SearchTermDescriptionGeneratorFactory.php new file mode 100644 index 0000000000000..fed645e975ed1 --- /dev/null +++ b/setup/src/Magento/Setup/Model/SearchTermDescriptionGeneratorFactory.php @@ -0,0 +1,147 @@ +objectManager = $objectManager; + $this->fixtureConfig = $fixtureConfig; + } + + /** + * Search term description factory + * + * @param array|null $descriptionConfig + * @param array|null $searchTermsConfig + * @param int $totalProductsCount + * @param string $defaultDescription + * @return DescriptionGeneratorInterface + */ + public function create( + $descriptionConfig, + $searchTermsConfig, + $totalProductsCount, + $defaultDescription = '' + ) { + $this->updateSearchTermConfig($searchTermsConfig); + if (empty($descriptionConfig) || empty($searchTermsConfig)) { + return $this->objectManager->create( + DefaultDescriptionGenerator::class, + ['defaultDescription' => $defaultDescription] + ); + } + return $this->objectManager->create(\Magento\Setup\Model\SearchTermDescriptionGenerator::class, [ + 'descriptionGenerator' => $this->buildDescriptionGenerator($descriptionConfig), + 'searchTermManager' => $this->buildSearchTermManager($searchTermsConfig, $totalProductsCount) + ]); + } + + /** + * Update search terms distribution to be almost the same per each website + * + * @param array|null $searchTermsConfig + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @return void + */ + private function updateSearchTermConfig(&$searchTermsConfig) + { + if (null !== $searchTermsConfig) { + $websitesCount = (bool)$this->fixtureConfig->getValue('assign_entities_to_all_websites', false) + ? 1 + : (int)$this->fixtureConfig->getValue('websites', 1); + array_walk( + $searchTermsConfig, + function (&$searchTerm, $key, $websitesCount) { + $searchTerm['count'] *= $websitesCount; + }, + $websitesCount + ); + } + } + + /** + * Builder for DescriptionGenerator + * + * @param array $descriptionConfig + * @return \Magento\Setup\Model\Description\DescriptionGenerator + */ + private function buildDescriptionGenerator(array $descriptionConfig) + { + $sentenceGeneratorFactory = $this->objectManager->create( + \Magento\Setup\Model\Description\DescriptionSentenceGeneratorFactory::class + ); + $paragraphGeneratorFactory = $this->objectManager->create( + \Magento\Setup\Model\Description\DescriptionParagraphGeneratorFactory::class + ); + $descriptionGeneratorFactory = $this->objectManager->create( + \Magento\Setup\Model\Description\DescriptionGeneratorFactory::class + ); + $dictionaryFactory = $this->objectManager->create( + \Magento\Setup\Model\DictionaryFactory::class + ); + + $sentenceGenerator = $sentenceGeneratorFactory->create([ + 'dictionary' => $dictionaryFactory->create([ + 'dictionaryFilePath' => realpath(__DIR__ . '/../Fixtures/_files/dictionary.csv') + ]), + 'sentenceConfig' => $descriptionConfig['paragraphs']['sentences'] + ]); + + $paragraphGenerator = $paragraphGeneratorFactory->create([ + 'sentenceGenerator' => $sentenceGenerator, + 'paragraphConfig' => $descriptionConfig['paragraphs'] + ]); + + $descriptionGenerator = $descriptionGeneratorFactory->create([ + 'paragraphGenerator' => $paragraphGenerator, + 'mixinManager' => $this->objectManager->create(\Magento\Setup\Model\Description\MixinManager::class), + 'descriptionConfig' => $descriptionConfig + ]); + + return $descriptionGenerator; + } + + /** + * Builder for SearchTermManager + * + * @param array $searchTermsConfig + * @param int $totalProductsCount + * @return \Magento\Setup\Model\SearchTermManager + */ + private function buildSearchTermManager(array $searchTermsConfig, $totalProductsCount) + { + $searchTermManagerFactory = $this->objectManager->get( + \Magento\Setup\Model\SearchTermManagerFactory::class + ); + + return $searchTermManagerFactory->create([ + 'searchTerms' => $searchTermsConfig, + 'totalProductsCount' => $totalProductsCount + ]); + } +} diff --git a/setup/src/Magento/Setup/Model/SearchTermManager.php b/setup/src/Magento/Setup/Model/SearchTermManager.php new file mode 100644 index 0000000000000..5ca68757c8934 --- /dev/null +++ b/setup/src/Magento/Setup/Model/SearchTermManager.php @@ -0,0 +1,83 @@ +searchTerms = $searchTerms; + $this->totalProductsCount = (int) $totalProductsCount; + } + + /** + * Apply search terms to product description + * based on search terms use distribution + * + * @param string $description + * @param int $currentProductIndex + * @return void + */ + public function applySearchTermsToDescription(&$description, $currentProductIndex) + { + if ($this->searchTermsUseRate === null) { + $this->calculateSearchTermsUseRate(); + } + + foreach ($this->searchTerms as &$searchTerm) { + if ( + $this->searchTermsUseRate[$searchTerm['term']]['use_rate'] > 0 + && $currentProductIndex % $this->searchTermsUseRate[$searchTerm['term']]['use_rate'] === 0 + && $this->searchTermsUseRate[$searchTerm['term']]['used'] < $searchTerm['count'] + ) { + $description .= ' ' . $searchTerm['term']; + $this->searchTermsUseRate[$searchTerm['term']]['used'] += 1; + } + } + } + + /** + * Calculates search terms use distribution + * based on total amount of products that will be generated + * and number of each search term + * + * @return void; + */ + private function calculateSearchTermsUseRate() + { + foreach ($this->searchTerms as $searchTerm) { + $this->searchTermsUseRate[$searchTerm['term']] = [ + 'use_rate' => floor($this->totalProductsCount / $searchTerm['count']), + 'used' => 0 + ]; + } + } +} diff --git a/setup/src/Magento/Setup/Model/StoreConfigurationDataMapper.php b/setup/src/Magento/Setup/Model/StoreConfigurationDataMapper.php index 30e7411248e93..83b745e7945eb 100644 --- a/setup/src/Magento/Setup/Model/StoreConfigurationDataMapper.php +++ b/setup/src/Magento/Setup/Model/StoreConfigurationDataMapper.php @@ -1,6 +1,6 @@ classesScanner->getList($path) as $className) { try { - if (!strpos($path, 'generation')) { // validate all classes except classes in var/generation dir + // validate all classes except classes in generated/code dir + $generatedCodeDir = DirectoryList::getDefaultConfig()[DirectoryList::GENERATED_CODE]; + if (!strpos($path, $generatedCodeDir[DirectoryList::PATH])) { $this->validator->validate($className); } $nameList[] = $className; diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php index da4839f494f6a..5a2cb8703f1a5 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php @@ -1,6 +1,6 @@ isScanned = true; } -} \ No newline at end of file +} diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/Type.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/Type.php index e05fffd0623f0..fecd61e4747b3 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/Type.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/Type.php @@ -1,7 +1,7 @@ initialize(); - $serialized = serialize($config); - file_put_contents($this->directoryList->getPath(DirectoryList::DI) . '/' . $key . '.ser', $serialized); + file_put_contents( + $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $key . '.ser', + $this->getSerializer()->serialize($config) + ); } /** @@ -50,8 +58,23 @@ public function write($key, array $config) */ private function initialize() { - if (!file_exists($this->directoryList->getPath(DirectoryList::DI))) { - mkdir($this->directoryList->getPath(DirectoryList::DI)); + if (!file_exists($this->directoryList->getPath(DirectoryList::GENERATED_METADATA))) { + mkdir($this->directoryList->getPath(DirectoryList::GENERATED_METADATA)); + } + } + + /** + * Get serializer + * + * @return SerializerInterface + * @deprecated + */ + private function getSerializer() + { + if (null === $this->serializer) { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() + ->get(Serialize::class); } + return $this->serializer; } } diff --git a/setup/src/Magento/Setup/Module/Di/Compiler/Config/WriterInterface.php b/setup/src/Magento/Setup/Module/Di/Compiler/Config/WriterInterface.php index 79e7272099704..8a1f28f62e0ff 100644 --- a/setup/src/Magento/Setup/Module/Di/Compiler/Config/WriterInterface.php +++ b/setup/src/Magento/Setup/Module/Di/Compiler/Config/WriterInterface.php @@ -1,7 +1,7 @@ _fileHandler); + if (is_resource($this->_fileHandler)) { + fclose($this->_fileHandler); + } + } + + /** + * Destructor for closing file handler + */ + public function __destruct() + { + if (is_resource($this->_fileHandler)) { + fclose($this->_fileHandler); + } } } diff --git a/setup/src/Magento/Setup/Module/I18n/Dictionary/Writer/Csv/Stdo.php b/setup/src/Magento/Setup/Module/I18n/Dictionary/Writer/Csv/Stdo.php index 2b75bf83b4a34..6f8be9c4c2789 100644 --- a/setup/src/Magento/Setup/Module/I18n/Dictionary/Writer/Csv/Stdo.php +++ b/setup/src/Magento/Setup/Module/I18n/Dictionary/Writer/Csv/Stdo.php @@ -1,6 +1,6 @@ getApplication(); $serviceManager = $application->getServiceManager(); + if ($serviceManager->get(\Magento\Framework\App\DeploymentConfig::class)->isAvailable()) { /** @var \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider */ $objectManagerProvider = $serviceManager->get(\Magento\Setup\Model\ObjectManagerProvider::class); @@ -135,17 +136,26 @@ public function authPreDispatch($event) 'appState' => $adminAppState ] ); - if (!$objectManager->get(\Magento\Backend\Model\Auth::class)->isLoggedIn()) { + /** @var \Magento\Backend\Model\Auth $auth */ + $authentication = $objectManager->get(\Magento\Backend\Model\Auth::class); + + if ( + !$authentication->isLoggedIn() || + !$adminSession->isAllowed('Magento_Backend::setup_wizard') + ) { $adminSession->destroy(); + /** @var \Zend\Http\Response $response */ $response = $event->getResponse(); $baseUrl = Http::getDistroBaseUrlPath($_SERVER); $response->getHeaders()->addHeaderLine('Location', $baseUrl . 'index.php/session/unlogin'); $response->setStatusCode(302); $event->stopPropagation(); + return $response; } } } + return false; } diff --git a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php index 027c3f8d36233..cebca17dfd571 100644 --- a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php +++ b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php @@ -1,6 +1,6 @@ fixtureModel->expects($this->once())->method('loadConfig')->with('path_to_profile.xml'); $this->fixtureModel->expects($this->once())->method('initObjectManager'); $this->fixtureModel->expects($this->once())->method('loadFixtures'); - + $commandTester = new CommandTester($this->command); $commandTester->execute(['profile' => 'path_to_profile.xml']); } /** * @expectedException \RuntimeException - * @expectedExceptionMessage Not enough arguments. + * @expectedExceptionMessage Not enough arguments */ public function testExecuteInvalidLanguageArgument() { diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoAdminUriCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoAdminUriCommandTest.php index f63c8cc8fcf59..0d0c3b9c3efd5 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoAdminUriCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoAdminUriCommandTest.php @@ -1,6 +1,6 @@ urlValidatorMock = $this->getMock(UrlValidator::class, [], [], '', false); + $this->localeValidatorMock = $this->getMock(LocaleValidator::class, [], [], '', false); + $this->timezoneValidatorMock = $this->getMock(TimezoneValidator::class, [], [], '', false); + $this->currencyValidatorMock = $this->getMock(CurrencyValidator::class, [], [], '', false); + $this->installerFactory = $this->getMock(\Magento\Setup\Model\InstallerFactory::class, [], [], '', false); $this->deploymentConfig = $this->getMock(\Magento\Framework\App\DeploymentConfig::class, [], [], '', false); $this->installer = $this->getMock(\Magento\Setup\Model\Installer::class, [], [], '', false); @@ -62,7 +93,11 @@ protected function setUp() $this->command = new InstallStoreConfigurationCommand( $this->installerFactory, $this->deploymentConfig, - $objectManagerProvider + $objectManagerProvider, + $this->localeValidatorMock, + $this->timezoneValidatorMock, + $this->currencyValidatorMock, + $this->urlValidatorMock ); } @@ -102,41 +137,11 @@ public function testExecuteNotInstalled() */ public function testExecuteInvalidData(array $option, $error) { - $url= $this->getMock(\Magento\Framework\Url\Validator::class, [], [], '', false); - $url->expects($this->any())->method('isValid')->will($this->returnValue(false)); - if (!isset($option['--' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE])) { - $url->expects($this->any())->method('getMessages')->will($this->returnValue([ - Validator::INVALID_URL => 'Invalid URL.' - ])); - } - $localeLists= $this->getMock(\Magento\Framework\Validator\Locale::class, [], [], '', false); - $localeLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - $timezoneLists= $this->getMock(\Magento\Framework\Validator\Timezone::class, [], [], '', false); - $timezoneLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - $currencyLists= $this->getMock(\Magento\Framework\Validator\Currency::class, [], [], '', false); - $currencyLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - - $returnValueMapOM = [ - [ - \Magento\Framework\Url\Validator::class, - $url - ], - [ - \Magento\Framework\Validator\Locale::class, - $localeLists - ], - [ - \Magento\Framework\Validator\Timezone::class, - $timezoneLists - ], - [ - \Magento\Framework\Validator\Currency::class, - $currencyLists - ], - ]; - $this->objectManager->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($returnValueMapOM)); + $this->localeValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->timezoneValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->currencyValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->urlValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->deploymentConfig->expects($this->once()) ->method('isAvailable') ->will($this->returnValue(true)); @@ -144,7 +149,7 @@ public function testExecuteInvalidData(array $option, $error) ->method('create'); $commandTester = new CommandTester($this->command); $commandTester->execute($option); - $this->assertEquals($error . PHP_EOL, $commandTester->getDisplay()); + $this->assertContains($error, $commandTester->getDisplay()); } /** @@ -155,48 +160,54 @@ public function validateDataProvider() return [ [ ['--' . StoreConfigurationDataMapper::KEY_BASE_URL => 'sampleUrl'], - 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL . '\': Invalid URL.' + 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL . '\': Invalid URL \'sampleUrl\'.' + ], + [ + ['--' . StoreConfigurationDataMapper::KEY_BASE_URL => 'http://example.com_test'], + 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL + . '\': Invalid URL \'http://example.com_test\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_LANGUAGE => 'sampleLanguage'], 'Command option \'' . StoreConfigurationDataMapper::KEY_LANGUAGE - . '\': Invalid value. To see possible values, run command \'bin/magento info:language:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:language:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_TIMEZONE => 'sampleTimezone'], 'Command option \'' . StoreConfigurationDataMapper::KEY_TIMEZONE - . '\': Invalid value. To see possible values, run command \'bin/magento info:timezone:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:timezone:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_CURRENCY => 'sampleLanguage'], 'Command option \'' . StoreConfigurationDataMapper::KEY_CURRENCY - . '\': Invalid value. To see possible values, run command \'bin/magento info:currency:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:currency:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_USE_SEF_URL => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_USE_SEF_URL - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_IS_SECURE => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_IS_SECURE - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE => 'http://www.sample.com'], 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': Invalid secure URL.' + . '\': Invalid URL \'http://www.sample.com\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], + ]; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/MaintenanceAllowIpsCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/MaintenanceAllowIpsCommandTest.php index 126cbadb6a53b..7e6cd17b2639b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/MaintenanceAllowIpsCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/MaintenanceAllowIpsCommandTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Setup\Model\InstallerFactory::class, [], [], '', false); $installer = $this->getMock(\Magento\Setup\Model\Installer::class, [], [], '', false); @@ -20,6 +25,25 @@ public function testExecute() $installer->expects($this->at(2))->method('installDataFixtures'); $installerFactory->expects($this->once())->method('create')->willReturn($installer); $commandTester = new CommandTester(new UpgradeCommand($installerFactory)); - $this->assertSame(Cli::RETURN_SUCCESS, $commandTester->execute([])); + $this->assertSame(Cli::RETURN_SUCCESS, $commandTester->execute($options)); + $this->assertEquals($expectedString, $commandTester->getDisplay()); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + [ + 'options' => [], + 'expectedString' => 'Please re-run Magento compile command. Use the command "setup:di:compile"' + . PHP_EOL + ], + [ + 'options' => ['--keep-generated' => true], + 'expectedString' => '' + ], + ]; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php index ce13e6977e773..2cb2ecf840f5f 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php @@ -1,6 +1,6 @@ serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + $this->serviceManagerMock = $this->getMockBuilder(ServiceManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock = $this->getMockBuilder(ArgvInput::class) ->disableOriginalConstructor() ->getMock(); - $this->inputMock = $this->getMockBuilder(\Symfony\Component\Console\Input\ArgvInput::class) + $this->filesystemDriverMock = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystemDriverMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Driver\File::class) + $this->generationDirectoryAccessMock = $this->getMockBuilder(GenerationDirectoryAccess::class) ->disableOriginalConstructor() ->getMock(); + $this->model = (new ObjectManager($this))->getObject( - \Magento\Setup\Console\CompilerPreparation::class, + CompilerPreparation::class, [ 'serviceManager' => $this->serviceManagerMock, 'input' => $this->inputMock, - 'filesystemDriver' => $this->filesystemDriverMock + 'filesystemDriver' => $this->filesystemDriverMock, + 'generationDirectoryAccess' => $this->generationDirectoryAccessMock, ] ); } @@ -56,24 +80,37 @@ public function setUp() */ public function testClearGenerationDirWhenNeeded($commandName, $isCompileCommand, $isHelpOption, $dirExists = false) { - $this->inputMock->expects($this->once())->method('getFirstArgument')->willReturn($commandName); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn($commandName); $this->inputMock->expects($this->atLeastOnce()) ->method('hasParameterOption') - ->with( - $this->logicalOr('--help', '-h') - )->willReturn($isHelpOption); + ->with($this->logicalOr('--help', '-h')) + ->willReturn($isHelpOption); + if ($isCompileCommand && !$isHelpOption) { $this->filesystemDriverMock->expects($this->exactly(2)) ->method('isExists') ->willReturn($dirExists); - $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2))->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2)) + ->method('deleteDirectory'); } else { - $this->filesystemDriverMock->expects($this->never())->method('isExists'); - $this->filesystemDriverMock->expects($this->never())->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->never()) + ->method('isExists'); + $this->filesystemDriverMock->expects($this->never()) + ->method('deleteDirectory'); } + + $this->generationDirectoryAccessMock->expects($this->any()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } + /** + * @return array + */ public function commandNameDataProvider() { return [ @@ -108,10 +145,6 @@ public function testGenerationDirectoryFromInitParams() $customGenerationDirectory = '/custom/generated/code/directory'; $defaultDiDirectory = '/custom/di/directory'; $mageInitParams = ['MAGE_DIRS' => ['generation' => ['path' => $customGenerationDirectory]]]; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirValueMap = [ [ $customGenerationDirectory, @@ -122,16 +155,24 @@ public function testGenerationDirectoryFromInitParams() true ] ]; - // Filesystem mock - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirValueMap)); - + ->willReturnMap($dirValueMap); $this->serviceManagerMock->expects($this->once()) ->method('get') ->with(InitParamListener::BOOTSTRAP_PARAM) ->willReturn($mageInitParams); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } @@ -139,10 +180,6 @@ public function testGenerationDirectoryFromCliOption() { $customGenerationDirectory = '/custom/generated/code/directory'; $customDiDirectory = '/custom/di/directory'; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirResultMap = [ [ $this->logicalNot($this->equalTo($customGenerationDirectory)), @@ -154,10 +191,18 @@ public function testGenerationDirectoryFromCliOption() ] ]; - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirResultMap)); + ->willReturnMap($dirResultMap); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); $this->model->handleCompilerEnvironment(); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php index 64c431d946ea2..4673cff9dd291 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php @@ -1,6 +1,6 @@ getMockBuilder(UrlValidator::class) + ->disableOriginalConstructor() + ->getMock(); + $validator->expects($this->any()) + ->method('isValid') + ->willReturnMap($returnMap); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $requestMock */ + $requestMock = $this->getMockBuilder(RequestInterface::class) + ->getMockForAbstractClass(); + $requestMock->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode($requestJson)); + + $controller = $objectManagerHelper->getObject( + UrlCheck::class, + ['urlValidator' => $validator] + ); + $objectManagerHelper->setBackwardCompatibleProperty($controller, 'request', $requestMock); + + $this->assertEquals(new JsonModel($expectedResult), $controller->indexAction()); + } + + /** + * @return array + */ + public function indexActionDataProvider() + { + return [ + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com_test' + ] + ], + 'expectedResult' => ['successUrl' => false, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com_test' + ], + 'https' => [ + 'admin' => false, + 'front' => false, + 'text' => '' + ] + ], + 'expectedResult' => ['successUrl' => false, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080' + ], + 'https' => [ + 'admin' => true, + 'front' => false, + 'text' => 'https://example.com.ua/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080/folder_name/' + ], + 'https' => [ + 'admin' => false, + 'front' => true, + 'text' => 'https://example.com.ua/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080/folder_name/' + ], + 'https' => [ + 'admin' => true, + 'front' => true, + 'text' => 'https://example.com.ua:8090/folder_name/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php index 829c64651e346..b6b5fc6ae6608 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php @@ -1,6 +1,6 @@ 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'attribute_code' => 'attribute_1', + 'is_searchable' => 0, + 'frontend_label' => 'Attribute 1', + 'frontend_input' => 'select', + 'backend_type' => 1, + 'default_option' => 'option 1', + 'options' => [ + 'option' => [ + [ + 'label' => 'option 1', + 'value' => 'option_1' + ], + ] + ] + ] + ] + ] + ]; + + // Mock Attribute Sets + $attributeSetMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeSetInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeSetMock->expects($this->once()) + ->method('setAttributeSetName') + ->with("attribute set name"); + $attributeSetMock->expects($this->once()) + ->method('setEntityTypeId') + ->with(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE); + $attributeSetMock->expects($this->any()) + ->method('getAttributeSetName') + ->willReturn($attributeSets['name']); + + $attributeSetFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeSetInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeSetFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeSetMock); + + $attributeSetManagementMock = $this->getMockBuilder(\Magento\Catalog\Api\AttributeSetManagementInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeSetManagementMock->expects($this->once()) + ->method('create') + ->with($attributeSetMock, '4') + ->willReturn($attributeSetMock); + + //Mock Attribute Groups + $attributeGroupMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeGroupInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeGroupMock->expects($this->once()) + ->method('setAttributeGroupName') + ->with($attributeSetMock->getAttributeSetName() . ' - Group'); + $attributeGroupMock->expects($this->once()) + ->method('setAttributeSetId') + ->with($attributeSetMock->getAttributeSetId()); + + $attributeGroupFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeGroupInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeGroupFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeGroupMock); + + $productAttributeGroupRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeGroupRepoMock->expects($this->once()) + ->method('save') + ->with($attributeGroupMock) + ->willReturn($attributeGroupMock); + + // Mock Attributes + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $attributeFactoryMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeMock); + + //Mock Attribute Options + $optionMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeOptionInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $optionFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeOptionInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $optionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($optionMock); + + $productAttributeRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeRepoMock->expects($this->once()) + ->method('save') + ->with($attributeMock) + ->willReturn($attributeMock); + + $productAttributeManagementMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeManagementInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeManagementMock->expects($this->once()) + ->method('assign') + ->willReturn($attributeMock->getAttributeId()); + + $attributeSet = new \Magento\Setup\Fixtures\AttributeSet\AttributeSetFixture( + $attributeSetManagementMock, + $productAttributeGroupRepoMock, + $productAttributeRepoMock, + $productAttributeManagementMock, + $attributeFactoryMock, + $optionFactoryMock, + $attributeSetFactoryMock, + $attributeGroupFactoryMock + ); + $attributeSet->createAttributeSet($attributeSets); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php new file mode 100644 index 0000000000000..e17b9071a2605 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php @@ -0,0 +1,62 @@ + 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'attribute_code' => 'attribute_1', + 'is_searchable' => 0, + 'frontend_label' => 'Attribute 1', + 'frontend_input' => 'select', + 'backend_type' => 1, + 'default_option' => 'option 1', + 'options' => [ + 'option' => [ + [ + 'label' => 'option 1', + 'value' => 'option_1' + ], + [ + 'label' => 'option 2', + 'value' => 'option_2' + ] + ] + ] + ] + ] + ] + ]; + $pattern = new \Magento\Setup\Fixtures\AttributeSet\Pattern(); + $this->assertEquals( + $attributeSets, + $pattern->generateAttributeSet( + 'attribute set name', + 1, + 2, + function ($index, $attributeData) { + $attributeData['backend_type'] = $index; + return $attributeData; + } + ) + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/SwatchesGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/SwatchesGeneratorTest.php new file mode 100644 index 0000000000000..89bd0efdf267d --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/SwatchesGeneratorTest.php @@ -0,0 +1,73 @@ +getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + // Mock Media Config + $mediaConfigMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + + // Mock Swatch Media Helper + $swatchHelperMock = $this->getMockBuilder(Media::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->swatchesGeneratorMock = new SwatchesGenerator( + $filesystemMock, + $mediaConfigMock, + $swatchHelperMock + ); + } + + public function testGenerateSwatchData() + { + + $attribute['swatch_input_type'] = Swatch::SWATCH_INPUT_TYPE_VISUAL; + $attribute['swatchvisual']['value'] = array_reduce( + range(1, 3), + function ($values, $index) { + $values['option_' . $index] = '#' . str_repeat(dechex(255 * $index / 3), 3); + return $values; + }, + [] + ); + + $attribute['optionvisual']['value'] = array_reduce( + range(1, 3), + function ($values, $index) { + $values['option_' . $index] = ['option ' . $index]; + return $values; + }, + [] + ); + + $this->assertEquals( + $attribute, + $this->swatchesGeneratorMock->generateSwatchData(3, 'test', 'color') + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php new file mode 100644 index 0000000000000..e9c4142fb813f --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php @@ -0,0 +1,112 @@ +fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeSetsFixtureMock = $this->getMockBuilder(AttributeSetFixture::class) + ->disableOriginalConstructor() + ->getMock(); + $this->patternMock = $this->getMockBuilder(\Magento\Setup\Fixtures\AttributeSet\Pattern::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new AttributeSetsFixture( + $this->fixtureModelMock, + $this->attributeSetsFixtureMock, + $this->patternMock + ); + } + + public function testCreateAttributeSet() + { + $valueMap = [ + ['attribute_sets', null, ['attribute_set' => [['some-data']]]], + ['product_attribute_sets', null, null], + ]; + + $this->attributeSetsFixtureMock->expects($this->once()) + ->method('createAttributeSet') + ->with(['some-data']); + $this->fixtureModelMock + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValueMap($valueMap)); + + $this->model->execute(); + } + + public function testCreateProductAttributeSet() + { + $valueMap = [ + ['attribute_sets', null, null], + ['product_attribute_sets', null, 1], + ['product_attribute_sets_attributes', 3, 2], + ['product_attribute_sets_attributes_values', 3, 3], + ]; + + $closure = function () { + }; + $this->patternMock->expects($this->once()) + ->method('generateAttributeSet') + ->with(\Magento\Setup\Fixtures\AttributeSetsFixture::PRODUCT_SET_NAME . 1, 2, 3, $closure) + ->willReturn(['some-data']); + $this->attributeSetsFixtureMock->expects($this->once()) + ->method('createAttributeSet') + ->with(['some-data']); + $this->fixtureModelMock + ->expects($this->exactly(4)) + ->method('getValue') + ->will($this->returnValueMap($valueMap)); + + $this->model->execute(); + } + + public function testGetActionTitle() + { + $this->assertSame('Generating attribute sets', $this->model->getActionTitle()); + } + + public function testIntroduceParamLabels() + { + $this->assertSame([ + 'attribute_sets' => 'Attribute Sets (Default)', + 'product_attribute_sets' => 'Attribute Sets (Extra)' + ], $this->model->introduceParamLabels()); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/CartPriceRulesFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/CartPriceRulesFixtureTest.php index 9a871cbd196d2..c8eb60180e843 100755 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/CartPriceRulesFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/CartPriceRulesFixtureTest.php @@ -1,6 +1,6 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); + $this->fixtureModelMock = $this->getMock(FixtureModel::class, [], [], '', false); + $this->collectionFactoryMock = $this->getMock(CollectionFactory::class, ['create'], [], '', false); + $this->collectionMock = $this->getMock(Collection::class, [], [], '', false); + $this->categoryFactoryMock = $this->getMock(CategoryFactory::class, ['create'], [], '', false); + + $this->model = (new ObjectManager($this))->getObject(CategoriesFixture::class, [ + 'fixtureModel' => $this->fixtureModelMock, + 'collectionFactory' => $this->collectionFactoryMock, + 'rootCategoriesIds' => [2], + 'categoryFactory' => $this->categoryFactoryMock, + 'firstLevelCategoryIndex' => 1, + ]); + } + + public function testDoNoExecuteIfCategoriesAlreadyGenerated() + { + $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($this->collectionMock); + $this->collectionMock->expects($this->once())->method('getSize')->willReturn(32); + $this->fixtureModelMock + ->expects($this->once()) + ->method('getValue') + ->willReturn(30); + $this->categoryFactoryMock->expects($this->never())->method('create'); - $this->model = new CategoriesFixture($this->fixtureModelMock); + $this->model->execute(); } + public function testExecute() { - $categoryMock = $this->getMock( + $valueMap = [ + ['categories', 0, 1], + ['categories_nesting_level', 3, 3] + ]; + + $this->fixtureModelMock + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValueMap($valueMap)); + + $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($this->collectionMock); + $this->collectionMock->expects($this->once())->method('getSize')->willReturn(2); + + $parentCategoryMock = $this->getMock( \Magento\Catalog\Model\Category::class, [ 'getName', 'setId', + 'getId', 'setUrlKey', 'setUrlPath', 'setName', 'setParentId', 'setPath', 'setLevel', + 'getLevel', 'setAvailableSortBy', 'setDefaultSortBy', 'setIsActive', - 'save' + 'setIsAnchor', + 'save', + 'setStoreId', + 'load', ], [], '', false ); + $parentCategoryMock->expects($this->once())->method('getId')->willReturn(5); + $parentCategoryMock->expects($this->once())->method('getLevel')->willReturn(3); + $categoryMock = clone $parentCategoryMock; $categoryMock->expects($this->once()) ->method('getName') + ->with('Category 1') ->will($this->returnValue('category_name')); $categoryMock->expects($this->once()) ->method('setId') @@ -65,12 +130,18 @@ public function testExecute() ->willReturnSelf(); $categoryMock->expects($this->once()) ->method('setParentId') + ->with(5) ->willReturnSelf(); $categoryMock->expects($this->once()) ->method('setPath') ->willReturnSelf(); + $categoryMock->expects($this->once()) + ->method('setIsAnchor') + ->with(true) + ->willReturnSelf(); $categoryMock->expects($this->once()) ->method('setLevel') + ->with(4) ->willReturnSelf(); $categoryMock->expects($this->once()) ->method('setAvailableSortBy') @@ -82,62 +153,7 @@ public function testExecute() ->method('setIsActive') ->willReturnSelf(); - $groupMock = $this->getMock(\Magento\Store\Model\Group::class, [], [], '', false); - $groupMock->expects($this->once()) - ->method('getRootCategoryId') - ->will($this->returnValue('root_category_id')); - - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getGroups') - ->will($this->returnValue([$groupMock])); - - $objectValueMock = [ - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock], - [\Magento\Catalog\Model\Category::class, [], $categoryMock] - ]; - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) - ->method('create') - ->will($this->returnValueMap($objectValueMock)); - - $valueMap = [ - ['categories', 0, 1], - ['categories_nesting_level', 3, 3] - ]; - - $this->fixtureModelMock - ->expects($this->exactly(2)) - ->method('getValue') - ->will($this->returnValueMap($valueMap)); - $this->fixtureModelMock - ->expects($this->exactly(2)) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); - - $this->model->execute(); - } - - public function testNoFixtureConfigValue() - { - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); - $categoryMock->expects($this->never())->method('save'); - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) - ->method('create') - ->with($this->equalTo(\Magento\Catalog\Model\Category::class)) - ->willReturn($categoryMock); - - $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManager') - ->willReturn($objectManagerMock); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); + $this->categoryFactoryMock->expects($this->once())->method('create')->willReturn($categoryMock); $this->model->execute(); } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigsApplyFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigsApplyFixtureTest.php index aed093dad90c5..c917188fff2e2 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigsApplyFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigsApplyFixtureTest.php @@ -1,6 +1,6 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); - - $this->model = new ConfigurableProductsFixture($this->fixtureModelMock); + $this->fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->setMethods(['createAttributeSet', 'getValue']) + ->getMock(); + + $this->attributeSetsFixtureMock = $this->getMockBuilder(AttributeSetFixture::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->attributePatternMock = $this->getMockBuilder(Pattern::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->attributePatternMock = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributePatternMock = $this->getMockBuilder(Pattern::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = (new ObjectManager($this))->getObject(ConfigurableProductsFixture::class, [ + 'fixtureModel' => $this->fixtureModelMock, + 'attributeSetsFixture' => $this->attributeSetsFixtureMock, + 'attributePattern' => $this->attributePatternMock, + ]); } + /** + * @SuppressWarnings(PHPMD) + */ public function testExecute() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); - - $contextMock = $this->getMock(\Magento\Framework\Model\ResourceModel\Db\Context::class, [], [], '', false); - $abstractDbMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - [$contextMock], - '', - true, - true, - true, - ['getAllChildren'] - ); - $abstractDbMock->expects($this->once()) - ->method('getAllChildren') - ->will($this->returnValue([1])); - - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); - $categoryMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($abstractDbMock)); - $categoryMock->expects($this->exactly(3)) - ->method('getName') - ->will($this->returnValue('category_name')); - $categoryMock->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('path/to/category')); - $categoryMock->expects($this->exactly(4)) - ->method('load') - ->willReturnSelf(); - - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); - $storeMock->expects($this->once()) - ->method('getRootCategoryId') - ->will($this->returnValue([2])); - - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); - $websiteMock->expects($this->once()) - ->method('getCode') - ->will($this->returnValue('website_code')); - $websiteMock->expects($this->once()) - ->method('getGroups') - ->will($this->returnValue([$storeMock])); - - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getWebsites') - ->will($this->returnValue([$websiteMock])); - - $source = $this->getMockBuilder(Generator::class)->disableOriginalConstructor()->getMock(); - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - - $objectManagerMock->expects($this->at(0)) - ->method('get') - ->with(\Magento\Store\Model\StoreManager::class) - ->willReturn($storeManagerMock); + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); - $objectManagerMock->expects($this->at(1)) - ->method('create') - ->will($this->returnValue($categoryMock)); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); - $objectManagerMock->expects($this->at(2)) - ->method('create') - ->with(\Magento\ImportExport\Model\Import::class) - ->willReturn($importMock); + $storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $source = $this->getMockBuilder(\Magento\Setup\Model\Complex\Generator::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $attributeSetRepositoryMock = $this->getMockForAbstractClass( + \Magento\Catalog\Api\AttributeSetRepositoryInterface::class + ); + + $productAttributeOptionManagementInterface = $this->getMockForAbstractClass( + \Magento\Catalog\Api\ProductAttributeOptionManagementInterface::class + ); - $objectManagerMock->expects($this->at(3)) + $objectManagerMock->expects($this->any()) + ->method('get') + ->will($this->returnValueMap([ + [ + \Magento\Store\Model\StoreManager::class, + $storeManagerMock + ], + [ + \Magento\Catalog\Api\AttributeSetRepositoryInterface::class, + $attributeSetRepositoryMock + ], + [ + \Magento\Catalog\Api\ProductAttributeOptionManagementInterface::class, + $productAttributeOptionManagementInterface + ] + ])); + + $attributeCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $objectManagerMock->expects($this->any()) ->method('create') - ->with(Generator::class) - ->willReturn($source); - $importMock->expects($this->once())->method('validateSource')->with($source)->willReturn(1); - $importMock->expects($this->once())->method('importSource')->willReturn(1); + ->will( + $this->returnCallback( + function ($className) use ( + $attributeCollectionFactoryMock, + $categoryMock, + $importMock, + $source + ) { + if ($className === CollectionFactory::class) { + return $attributeCollectionFactoryMock; + } + + if ($className === \Magento\Catalog\Model\Category::class) { + return $categoryMock; + } + + if ($className === \Magento\ImportExport\Model\Import::class) { + return $importMock; + } + + if ($className === \Magento\Setup\Model\Complex\Generator::class) { + return $source; + } + + return null; + } + ) + ); + + $valuesMap = [ + ['configurable_products', 0, 1], + ['simple_products', 0, 1], + ['search_terms', null, ['search_term' =>[['term' => 'iphone 6', 'count' => '1']]]], + ['configurable_products_variation', 3, 1], + [ + 'search_config', + null, + [ + 'max_amount_of_words_description' => '200', + 'max_amount_of_words_short_description' => '20', + 'min_amount_of_words_description' => '20', + 'min_amount_of_words_short_description' => '5' + ] + ], + ['attribute_sets', + null, + [ + 'attribute_set' => [ + [ + 'name' => 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'default_value' => 'yellow1', + 'attribute_code' => 'mycolor', + 'is_searchable' => '1', + 'frontend_label' => 'mycolor', + 'frontend_input' => 'select', + 'options' => [ + 'option' => [ + [ + 'label' => 'yellow1', + 'value' => '' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; $this->fixtureModelMock ->expects($this->any()) ->method('getValue') - ->willReturnMap([ - ['configurable_products', 0, 1], - ['configurable_products_variation', 3, 1], - ]); - - $this->fixtureModelMock - ->expects($this->atLeastOnce()) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); + ->will($this->returnValueMap($valuesMap)); $this->model->execute(); } public function testNoFixtureConfigValue() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); $importMock->expects($this->never())->method('validateSource'); $importMock->expects($this->never())->method('importSource'); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); $objectManagerMock->expects($this->never()) ->method('create') ->with($this->equalTo(\Magento\ImportExport\Model\Import::class)) @@ -135,10 +231,6 @@ public function testNoFixtureConfigValue() ->expects($this->never()) ->method('getObjectManager') ->will($this->returnValue($objectManagerMock)); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); $this->model->execute(); } @@ -150,8 +242,6 @@ public function testGetActionTitle() public function testIntroduceParamLabels() { - $this->assertSame([ - 'configurable_products' => 'Configurable products', - ], $this->model->introduceParamLabels()); + $this->assertSame([], $this->model->introduceParamLabels()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomerGroupsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomerGroupsFixtureTest.php new file mode 100644 index 0000000000000..1e2c90be42606 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomerGroupsFixtureTest.php @@ -0,0 +1,123 @@ +fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + + //Mock repository for customer groups + $this->groupRepositoryMock = $this->getMockBuilder(GroupRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + //Mock for customer groups collection + $this->groupCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create', 'getSize']) + ->disableOriginalConstructor() + ->getMock(); + + $this->groupCollectionFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->groupCollectionFactoryMock); + + $this->groupCollectionFactoryMock + ->expects($this->once()) + ->method('getSize') + ->willReturn(0); + + //Mock customer groups data object + $this->groupDataObjectMock = $this->getMockBuilder(GroupInterface::class) + ->setMethods(['setCode', 'setTaxClassId', 'save']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + //Mock customer groups factory + $this->groupFactoryMock = $this->getMockBuilder(GroupInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->groupFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->groupDataObjectMock); + + $this->groupDataObjectMock + ->expects($this->once()) + ->method('setCode') + ->willReturn($this->groupDataObjectMock); + + $this->groupDataObjectMock + ->expects($this->once()) + ->method('setTaxClassId') + ->willReturn($this->groupDataObjectMock); + + $this->groupRepositoryMock + ->expects($this->once()) + ->method('save') + ->willReturn($this->groupDataObjectMock); + + $this->fixtureModelMock + ->expects($this->once()) + ->method('getValue') + ->will($this->returnValue(1)); + + $this->model = new CustomerGroupsFixture( + $this->fixtureModelMock, + $this->groupCollectionFactoryMock, + $this->groupRepositoryMock, + $this->groupFactoryMock + ); + + $this->model->execute(); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomersFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomersFixtureTest.php index 2ec140b4412ed..732ae4c2bbd51 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomersFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/CustomersFixtureTest.php @@ -1,11 +1,12 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); - - $this->model = new CustomersFixture($this->fixtureModelMock); + $this->fixtureModelMock = $this->getMock( + \Magento\Setup\Fixtures\FixtureModel::class, + [], + [], + '', + false + ); + + $this->customerGeneratorMock = $this->getMock( + \Magento\Setup\Model\FixtureGenerator\CustomerGenerator::class, + [], + [], + '', + false + ); + + $this->customerDataGeneratorFactoryMock = $this->getMock( + \Magento\Setup\Model\Customer\CustomerDataGeneratorFactory::class, + [], + [], + '', + false + ); + + $this->collectionFactoryMock = $this->getMock( + \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class, + ['create'], + [], + '', + false + ); + + $this->collectionMock = $this->getMock( + \Magento\Customer\Model\ResourceModel\Customer\Collection::class, + [], + [], + '', + false + ); + + $this->model = (new ObjectManager($this))->getObject(CustomersFixture::class, [ + 'fixtureModel' => $this->fixtureModelMock, + 'customerGenerator' => $this->customerGeneratorMock, + 'customerDataGeneratorFactory' => $this->customerDataGeneratorFactoryMock, + 'collectionFactory' => $this->collectionFactoryMock + ]); } public function testExecute() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); - - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); - $storeMock->expects($this->once()) - ->method('getCode') - ->will($this->returnValue('store_code')); - - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); - $websiteMock->expects($this->once()) - ->method('getCode') - ->will($this->returnValue('website_code')); - - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getDefaultStoreView') - ->will($this->returnValue($storeMock)); - $storeManagerMock->expects($this->once()) - ->method('getWebsites') - ->will($this->returnValue([$websiteMock])); - - $valueMap = [ - [ - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'customer_composite', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors' - ] - ], - $importMock - ], - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock] + $entitiesInDB = 20; + $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($this->collectionMock); + $this->collectionMock->expects($this->once())->method('getSize')->willReturn($entitiesInDB); + + $customersNumber = 100500; + $customerConfig = [ + 'some-key' => 'some value' ]; - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) + $this->fixtureModelMock + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->onConsecutiveCalls($customersNumber, $customerConfig)); + + $customerDataGeneratorMock = $this->getMock( + \Magento\Setup\Model\Customer\CustomerDataGenerator::class, + [], + [], + '', + false + ); + + $this->customerDataGeneratorFactoryMock + ->expects($this->once()) ->method('create') - ->will($this->returnValueMap($valueMap)); + ->with($customerConfig) + ->willReturn($customerDataGeneratorMock); - $this->fixtureModelMock + $this->customerGeneratorMock ->expects($this->once()) - ->method('getValue') - ->will($this->returnValue(1)); - $this->fixtureModelMock - ->expects($this->exactly(2)) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); + ->method('generate') + ->with( + $customersNumber - $entitiesInDB, + $this->arrayHasKey('customer_data') + ); $this->model->execute(); } - public function testNoFixtureConfigValue() + public function testDoNoExecuteIfCustomersAlreadyGenerated() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); - $importMock->expects($this->never())->method('validateSource'); - $importMock->expects($this->never())->method('importSource'); - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) - ->method('create') - ->with($this->equalTo(\Magento\ImportExport\Model\Import::class)) - ->willReturn($importMock); - - $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); + $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($this->collectionMock); + $this->collectionMock->expects($this->once())->method('getSize')->willReturn(20); $this->fixtureModelMock ->expects($this->once()) ->method('getValue') - ->willReturn(false); + ->willReturn(20); + $this->customerDataGeneratorFactoryMock->expects($this->never())->method('create'); $this->model->execute(); } @@ -112,8 +152,11 @@ public function testGetActionTitle() public function testIntroduceParamLabels() { - $this->assertSame([ - 'customers' => 'Customers' - ], $this->model->introduceParamLabels()); + $this->assertSame( + [ + 'customers' => 'Customers' + ], + $this->model->introduceParamLabels() + ); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/EavVariationsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/EavVariationsFixtureTest.php index d7d127ddd3b72..81996008f1bb2 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/EavVariationsFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/EavVariationsFixtureTest.php @@ -1,18 +1,24 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); - - $this->model = new EavVariationsFixture($this->fixtureModelMock); - } + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; - public function testExecute() - { - $attributeMock = $this->getMock( - \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, - [ - 'setAttributeSetId', - 'setAttributeGroupId', - 'save', - ], - [], - '', - false - ); - $attributeMock->expects($this->exactly(2)) - ->method('setAttributeSetId') - ->willReturnSelf(); - $attributeMock->expects($this->once()) - ->method('setAttributeGroupId') - ->willReturnSelf(); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $attributeSetMock; - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $cacheMock; - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getStores') - ->will($this->returnValue([$storeMock])); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfigMock; - $setMock = $this->getMock(\Magento\Eav\Model\Entity\Attribute\Set::class, [], [], '', false); - $setMock->expects($this->once()) - ->method('getDefaultGroupId') - ->will($this->returnValue(2)); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $attributeFactoryMock; - $cacheMock = $this->getMock(\Magento\Framework\App\CacheInterface::class, [], [], '', false); + public function setUp() + { + $this->fixtureModelMock = $this->getMock(FixtureModel::class, [], [], '', false); + $this->eavConfigMock = $this->getMock(Config::class, [], [], '', false); + $this->storeManagerMock = $this->getMock(StoreManager::class, [], [], '', false); + $this->attributeSetMock = $this->getMock(Set::class, [], [], '', false); + $this->cacheMock = $this->getMock(CacheInterface::class, [], [], '', false); + $this->attributeFactoryMock = $this->getMock(AttributeFactory::class, ['create'], [], '', false); + + $this->model = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + EavVariationsFixture::class, + [ + 'fixtureModel' => $this->fixtureModelMock, + 'eavConfig' => $this->eavConfigMock, + 'storeManager' => $this->storeManagerMock, + 'attributeSet' => $this->attributeSetMock, + 'cache' => $this->cacheMock, + 'attributeFactory' => $this->attributeFactoryMock, + ] + ); + } - $valueMap = [ - [\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, [], $attributeMock], - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock], - [\Magento\Eav\Model\Entity\Attribute\Set::class, $setMock], - [\Magento\Framework\App\CacheInterface::class, $cacheMock], - ]; + public function testDoNotExecuteWhenAttributeAleadyExist() + { + $this->fixtureModelMock + ->expects($this->any()) + ->method('getValue') + ->with('configurable_products', []) + ->willReturn(10); + $this->eavConfigMock->expects($this->once())->method('getEntityAttributeCodes') + ->willReturn(['configurable_variation']); + $this->attributeFactoryMock->expects($this->never())->method('create'); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) - ->method('create') - ->will($this->returnValueMap($valueMap)); - $objectManagerMock->expects($this->exactly(2)) - ->method('get') - ->will($this->returnValueMap($valueMap)); + $this->model->execute(); + } + public function testExecute() + { + $this->eavConfigMock->expects($this->once())->method('getEntityAttributeCodes') + ->willReturn(['attr1', 'attr2']); $this->fixtureModelMock ->expects($this->any()) ->method('getValue') ->willReturnMap([ - ['configurable_products', 0, 1], + ['configurable_products', [], ['some-config']], ['configurable_products_variation', 3, 1], ]); - $this->fixtureModelMock - ->expects($this->exactly(4)) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); - - $this->model->execute(); - } - - public function testNoFixtureConfigValue() - { - $attributeMock = $this->getMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, [], [], '', false); - $attributeMock->expects($this->never())->method('save'); + $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); + $this->storeManagerMock->expects($this->once()) + ->method('getStores') + ->will($this->returnValue([$storeMock])); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) - ->method('create') - ->with($this->equalTo(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)) - ->willReturn($attributeMock); + $this->attributeSetMock->expects($this->once())->method('load')->willReturnSelf(); + $this->attributeSetMock->expects($this->once()) + ->method('getDefaultGroupId') + ->will($this->returnValue(2)); - $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->setMethods([ + 'setAttributeSetId', + 'setAttributeGroupId', + 'save', + ])->disableOriginalConstructor() + ->getMock(); + $attributeMock->expects($this->exactly(2)) + ->method('setAttributeSetId') + ->willReturnSelf(); + $attributeMock->expects($this->once()) + ->method('setAttributeGroupId') + ->willReturnSelf(); + $this->attributeFactoryMock->expects($this->once())->method('create') + ->with( + [ + 'data' => [ + 'frontend_label' => [ + 'configurable variations', + 'configurable variations', + ], + 'frontend_input' => 'select', + 'is_required' => '0', + 'option' => [ + 'order' => ['option_1' => 1], + 'value' => ['option_1' => ['option 1', 'option 1']], + 'delete' => ['option_1' => ''], + ], + 'default' => ['option_0'], + 'attribute_code' => 'configurable_variation', + 'is_global' => '1', + 'default_value_text' => '', + 'default_value_yesno' => '0', + 'default_value_date' => '', + 'default_value_textarea' => '', + 'is_unique' => '0', + 'is_searchable' => '1', + 'is_visible_in_advanced_search' => '0', + 'is_comparable' => '0', + 'is_filterable' => '1', + 'is_filterable_in_search' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '0', + 'source_model' => null, + 'backend_model' => null, + 'apply_to' => [], + 'backend_type' => 'int', + 'entity_type_id' => 4, + 'is_user_defined' => 1, + 'swatch_input_type' => 'visual', + 'swatchvisual' => [ + 'value' => ['option_1' => '#ffffff'], + ], + 'optionvisual' => [ + 'value' => ['option_1' => ['option 1']], + ], + ] + ] + )->willReturn($attributeMock); + $this->cacheMock->expects($this->once())->method('remove')->with(Config::ATTRIBUTES_CACHE_ID . Product::ENTITY); $this->model->execute(); } public function testGetActionTitle() { - $eavVariationsFixture = new EavVariationsFixture($this->fixtureModelMock); - $this->assertSame('Generating configurable EAV variations', $eavVariationsFixture->getActionTitle()); + $this->assertSame('Generating configurable EAV variations', $this->model->getActionTitle()); } public function testIntroduceParamLabels() { - $eavVariationsFixture = new EavVariationsFixture($this->fixtureModelMock); - $this->assertSame([], $eavVariationsFixture->introduceParamLabels()); + $this->assertSame([], $this->model->introduceParamLabels()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureConfigTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureConfigTest.php new file mode 100644 index 0000000000000..134773e439eb8 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureConfigTest.php @@ -0,0 +1,80 @@ +fileParserMock = $this->getMock(Parser::class, ['getDom', 'xmlToArray'], [], '', false); + + $this->model = new FixtureConfig($this->fileParserMock); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Profile configuration file `exception.file` is not readable or does not exists. + */ + public function testLoadConfigException() + { + $this->model->loadConfig('exception.file'); + } + + public function testLoadConfig() + { + $this->fileParserMock->expects($this->exactly(2))->method('xmlToArray')->willReturn( + ['config' => [ 'profile' => ['some_key' => 'some_value']]] + ); + + $domMock = $this->getMock(\DOMDocument::class, ['load', 'xinclude'], [], '', false); + $domMock->expects($this->once())->method('load')->with('config.file')->willReturn( + $this->fileParserMock->xmlToArray() + ); + $domMock->expects($this->once())->method('xinclude'); + $this->fileParserMock->expects($this->exactly(2))->method('getDom')->willReturn($domMock); + + $this->model->loadConfig('config.file'); + $this->assertSame('some_value', $this->model->getValue('some_key')); + } + + public function testGetValue() + { + $this->assertSame(null, $this->model->getValue('null_key')); + } +} + +namespace Magento\Setup\Fixtures; + +/** + * Overriding the built-in PHP function since it cannot be mocked-> + * + * The method is used in FixtureModel. loadConfig in an if statement. By overriding this method we are able to test + * both of the possible cases based on the return value of is_readable. + * + * @param string $filename + * @return bool + */ +function is_readable($filename) +{ + if (strpos($filename, 'exception') !== false) { + return false; + } + return true; +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php index ee1369bd15d5a..76f22d23c8186 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Framework\Xml\Parser::class, [], [], '', false); - - $this->model = new FixtureModel($reindexCommandMock, $fileParserMock); + $this->model = new FixtureModel($reindexCommandMock); } public function testReindex() @@ -35,57 +32,4 @@ public function testReindex() $outputMock = $this->getMock(\Symfony\Component\Console\Output\OutputInterface::class, [], [], '', false); $this->model->reindex($outputMock); } - - /** - * @expectedException \Exception - * @expectedExceptionMessage Profile configuration file `exception.file` is not readable or does not exists. - */ - public function testLoadConfigException() - { - $this->model->loadConfig('exception.file'); - } - - public function testLoadConfig() - { - $reindexCommandMock = $this->getMock( - \Magento\Indexer\Console\Command\IndexerReindexCommand::class, - [], - [], - '', - false - ); - - $fileParserMock = $this->getMock(\Magento\Framework\Xml\Parser::class, ['load', 'xmlToArray'], [], '', false); - $fileParserMock->expects($this->once())->method('xmlToArray')->willReturn( - ['config' => [ 'profile' => ['some_key' => 'some_value']]] - ); - $fileParserMock->expects($this->once())->method('load')->with('config.file')->willReturn($fileParserMock); - $this->model = new FixtureModel($reindexCommandMock, $fileParserMock); - $this->model->loadConfig('config.file'); - $this->assertSame('some_value', $this->model->getValue('some_key')); - } - - public function testGetValue() - { - $this->assertSame(null, $this->model->getValue('null_key')); - } -} - -namespace Magento\Setup\Fixtures; - -/** - * Overriding the built-in PHP function since it cannot be mocked-> - * - * The method is used in FixtureModel. loadConfig in an if statement. By overriding this method we are able to test - * both of the possible cases based on the return value of is_readable. - * - * @param string $filename - * @return bool - */ -function is_readable($filename) -{ - if (strpos($filename, 'exception') !== false) { - return false; - } - return true; } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/IndexersStatesApplyFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/IndexersStatesApplyFixtureTest.php index 56fa29d6d4b8c..12c4f00ef0e85 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/IndexersStatesApplyFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/IndexersStatesApplyFixtureTest.php @@ -1,6 +1,6 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); - - $this->model = new OrdersFixture($this->fixtureModelMock); - } - - /** - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ public function testExecute() { - $mockObjectNames = [ - \Magento\Quote\Model\ResourceModel\Quote::class, - \Magento\Quote\Model\ResourceModel\Quote\Address::class, - \Magento\Quote\Model\ResourceModel\Quote\Item::class, - \Magento\Quote\Model\ResourceModel\Quote\Item\Option::class, - \Magento\Quote\Model\ResourceModel\Quote\Payment::class, - \Magento\Quote\Model\ResourceModel\Quote\Address\Rate::class, - \Magento\Reports\Model\ResourceModel\Event::class, - \Magento\Sales\Model\ResourceModel\Order::class, - \Magento\Sales\Model\ResourceModel\Order\Grid::class, - \Magento\Sales\Model\ResourceModel\Order\Item::class, - \Magento\Sales\Model\ResourceModel\Order\Payment::class, - \Magento\Sales\Model\ResourceModel\Order\Status\History::class, - \Magento\Eav\Model\ResourceModel\Entity\Store::class - ]; - $mockObjects = []; - - foreach ($mockObjectNames as $mockObjectName) { - $mockObject = $this->getMock($mockObjectName, ['getTable'], [], '', false); - $path = explode('\\', $mockObjectName); - $name = array_pop($path); - if (strcasecmp($mockObjectName, \Magento\Sales\Model\ResourceModel\Order::class) == 0) { - $mockObject->expects($this->exactly(2)) - ->method('getTable') - ->willReturn(strtolower($name) . '_table_name'); - } else { - $mockObject->expects($this->once()) - ->method('getTable') - ->willReturn(strtolower($name) . '_table_name'); - } - $mockObjects[] = [$mockObjectName, $mockObject]; - } - - $connectionInterfaceMock = $this->getMockForAbstractClass( - \Magento\Framework\DB\Adapter\AdapterInterface::class, - [], - '', - true, - true, - true, - [] + $storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $productCollectionFactoryMock = $this->getMockBuilder( + CollectionFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $productRepositoryMock = $this->getMockBuilder(ProductRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $optionRepositoryMock = $this->getMockBuilder(OptionRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $linkManagementMock = $this->getMockBuilder(LinkManagementInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $serializerMock = $this->getMockBuilder(SerializerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->fixtureModelMock = $this->getMockBuilder(FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new OrdersFixture( + $storeManagerMock, + $productCollectionFactoryMock, + $productRepositoryMock, + $optionRepositoryMock, + $linkManagementMock, + $serializerMock, + $this->fixtureModelMock ); - $connectionInterfaceMock->expects($this->exactly(14)) - ->method('getTableName') - ->willReturn('table_name'); - $resourceMock = $this->getMock(\Magento\Framework\App\ResourceConnection::class, [], [], '', false); - $resourceMock->expects($this->exactly(15)) - ->method('getConnection') - ->willReturn($connectionInterfaceMock); - - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, ['getId', 'getName'], [], '', false); - $websiteMock->expects($this->once()) - ->method('getId') - ->willReturn('website_id'); - $websiteMock->expects($this->once()) - ->method('getName') - ->willReturn('website_name'); - - $groupMock = $this->getMock(\Magento\Store\Model\Group::class, ['getName'], [], '', false); - $groupMock->expects($this->once()) - ->method('getName') - ->willReturn('group_name'); - - $storeMock = $this->getMock( - \Magento\Store\Model\Store::class, - [ - 'getStoreId', - 'getWebsite', - 'getGroup', - 'getName', - 'getRootCategoryId' - ], + $orderMock = $this->getMock( + Order::class, + ['getTable', 'getConnection', 'getTableName', 'query', 'fetchColumn'], [], '', false ); - $storeMock->expects($this->once()) - ->method('getStoreId') - ->willReturn(1); - $storeMock->expects($this->exactly(2)) - ->method('getWebsite') - ->willReturn($websiteMock); - $storeMock->expects($this->once()) - ->method('getGroup') - ->willReturn($groupMock); - $storeMock->expects($this->once()) - ->method('getName') - ->willReturn('store_name'); - $storeMock->expects($this->once()) - ->method('getRootCategoryId') - ->willReturn(1); - - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getStores') - ->willReturn([$storeMock]); - - $contextMock = $this->getMock(\Magento\Framework\Model\ResourceModel\Db\Context::class, [], [], '', false); - $abstractDbMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - [$contextMock], - '', - true, - true, - true, - ['getAllChildren'] - ); - $abstractDbMock->expects($this->once()) - ->method('getAllChildren') - ->will($this->returnValue([1])); - - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); - $categoryMock->expects($this->once()) - ->method('getResource') - ->willReturn($abstractDbMock); - $categoryMock->expects($this->once()) - ->method('getPath') - ->willReturn('path/to/category'); - $categoryMock->expects($this->exactly(2)) - ->method('getName') - ->willReturn('category_name'); - $categoryMock->expects($this->exactly(5)) - ->method('load') - ->willReturnSelf(); - - $productMock = - $this->getMock(\Magento\Catalog\Model\Product::class, ['load', 'getSku', 'getName'], [], '', false); - $productMock->expects($this->exactly(2)) - ->method('load') - ->willReturnSelf(); - $productMock->expects($this->exactly(2)) - ->method('getSku') - ->willReturn('product_sku'); - $productMock->expects($this->exactly(2)) - ->method('getName') - ->willReturn('product_name'); - - $selectMock = $this->getMock(\Magento\Framework\DB\Select::class, [], [], '', false); - - $collectionMock = - $this->getMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class, [], [], '', false); - $collectionMock->expects($this->once()) - ->method('getSelect') - ->willReturn($selectMock); - $collectionMock->expects($this->once()) - ->method('getAllIds') - ->willReturn([1, 1]); - - array_push( - $mockObjects, - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock], - [\Magento\Catalog\Model\Category::class, $categoryMock], - [\Magento\Catalog\Model\Product::class, $productMock], - [\Magento\Framework\App\ResourceConnection::class, $resourceMock], - [\Magento\Catalog\Model\ResourceModel\Product\Collection::class, [], $collectionMock] - ); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(32)) - ->method('get') - ->will($this->returnValueMap($mockObjects)); - $objectManagerMock->expects($this->exactly(2)) - ->method('create') - ->will($this->returnValueMap($mockObjects)); + $path = explode('\\', Order::class); + $name = array_pop($path); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(1); - $this->fixtureModelMock - ->expects($this->exactly(34)) - ->method('getObjectManager') - ->willReturn($objectManagerMock); - - $this->model->execute(); - } - - public function testNoFixtureConfigValue() - { - $connectionMock = $this->getMockForAbstractClass( - \Magento\Framework\DB\Adapter\AdapterInterface::class, - [], - '', - true, - true, - true, - [] - ); - $connectionMock->expects($this->never()) - ->method('query'); - - $resourceMock = $this->getMock(\Magento\Framework\App\ResourceConnection::class, [], [], '', false); - $resourceMock->expects($this->never()) + $orderMock->expects($this->atLeastOnce()) ->method('getConnection') - ->with($this->equalTo('write')) - ->willReturn($connectionMock); + ->willReturn($orderMock); + $orderMock->expects($this->once()) + ->method('getTable') + ->willReturn(strtolower($name) . '_table_name'); + $orderMock->expects($this->once()) + ->method('query') + ->willReturn($orderMock); + $orderMock->expects($this->once()) + ->method('getTableName') + ->willReturn(strtolower($name) . '_table_name'); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) + $objectManagerMock = $this->getMock(ObjectManager::class, [], [], '', false); + $objectManagerMock->expects($this->atLeastOnce()) ->method('get') - ->with($this->equalTo(\Magento\Framework\App\ResourceConnection::class)) - ->willReturn($resourceMock); + ->willReturn($orderMock); $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManagerMock') + ->expects($this->atLeastOnce()) + ->method('getObjectManager') ->willReturn($objectManagerMock); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); $this->model->execute(); } - - public function testGetActionTitle() - { - $this->assertSame('Generating orders', $this->model->getActionTitle()); - } - - public function testIntroduceParamLabels() - { - $this->assertSame([ - 'orders' => 'Orders' - ], $this->model->introduceParamLabels()); - } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php deleted file mode 100644 index 913b2a1e3f020..0000000000000 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php +++ /dev/null @@ -1,150 +0,0 @@ -fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); - - $this->model = new SimpleProductsFixture($this->fixtureModelMock); - } - - public function testExecute() - { - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); - $storeMock->expects($this->once()) - ->method('getRootCategoryId') - ->willReturn(1); - - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); - $websiteMock->expects($this->once()) - ->method('getCode') - ->willReturn('website_code'); - $websiteMock->expects($this->once()) - ->method('getGroups') - ->willReturn([$storeMock]); - - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getWebsites') - ->willReturn([$websiteMock]); - - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); - - $contextMock = $this->getMock(\Magento\Framework\Model\ResourceModel\Db\Context::class, [], [], '', false); - $abstractDbMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - [$contextMock], - '', - true, - true, - true, - ['getAllChildren'] - ); - $abstractDbMock->expects($this->once()) - ->method('getAllChildren') - ->will($this->returnValue([1])); - - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); - $categoryMock->expects($this->once()) - ->method('getResource') - ->willReturn($abstractDbMock); - $categoryMock->expects($this->once()) - ->method('getPath') - ->willReturn('path/to/category'); - $categoryMock->expects($this->exactly(5)) - ->method('load') - ->willReturnSelf(); - $categoryMock->expects($this->exactly(3)) - ->method('getName') - ->willReturn('category_name'); - - $valueMap = [ - [ - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'catalog_product', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors' - ] - ], - $importMock - ], - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock] - ]; - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) - ->method('create') - ->will($this->returnValueMap($valueMap)); - $objectManagerMock->expects($this->once()) - ->method('get') - ->willReturn($categoryMock); - - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(1); - $this->fixtureModelMock - ->expects($this->exactly(3)) - ->method('getObjectManager') - ->willReturn($objectManagerMock); - - $this->model->execute(); - } - - public function testNoFixtureConfigValue() - { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); - $importMock->expects($this->never())->method('validateSource'); - $importMock->expects($this->never())->method('importSource'); - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) - ->method('create') - ->with($this->equalTo(\Magento\ImportExport\Model\Import::class)) - ->willReturn($importMock); - - $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManager') - ->will($this->returnValue($objectManagerMock)); - $this->fixtureModelMock - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); - - $this->model->execute(); - } - - public function testGetActionTitle() - { - $this->assertSame('Generating simple products', $this->model->getActionTitle()); - } - - public function testIntroduceParamLabels() - { - $this->assertSame([ - 'simple_products' => 'Simple products' - ], $this->model->introduceParamLabels()); - } -} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/StoresFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/StoresFixtureTest.php index 7c1c37cc81a5e..dbd22962a23f1 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/StoresFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/StoresFixtureTest.php @@ -1,18 +1,28 @@ fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); + /** + * @var StoreManager + */ + private $storeManagerMock; - $this->model = new StoresFixture($this->fixtureModelMock); - } + /** + * @var ManagerInterface + */ + private $eventManagerMock; /** - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @var CategoryFactory */ - public function testExecute() - { - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); - $websiteMock->expects($this->exactly(2)) - ->method('getId') - ->willReturn('website_id'); - $websiteMock->expects($this->once()) - ->method('save'); + private $categoryFactoryMock; - $groupMock = $this->getMock(\Magento\Store\Model\Group::class, [], [], '', false); - $groupMock->expects($this->exactly(2)) - ->method('getId') - ->willReturn('group_id'); - $groupMock->expects($this->once()) - ->method('save'); + /** + * @var Writer + */ + private $scopeConfigMock; - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); - $storeMock->expects($this->once()) - ->method('getRootCategoryId') - ->willReturn(1); - $storeMock->expects($this->once()) - ->method('getId') - ->willReturn('store_id'); - $storeMock->expects($this->once()) - ->method('save'); + /** + * @var Config + */ + private $localeConfigMock; - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->once()) - ->method('getWebsite') - ->willReturn($websiteMock); - $storeManagerMock->expects($this->once()) - ->method('getGroup') - ->willReturn($groupMock); - $storeManagerMock->expects($this->once()) - ->method('getDefaultStoreView') - ->willReturn($storeMock); - $storeManagerMock->expects($this->once()) - ->method('getStore') - ->willReturn($storeMock); + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testExecute() + { + $this->fixtureModelMock = $this->getMockBuilder(FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManagerMock = $this->getMockBuilder(StoreManager::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'getGroup', + 'getGroups', + 'getWebsite', + 'getDefaultStoreView', + 'getStore', + 'getStores', + 'reinitStores' + ] + )->getMock(); + + $this->eventManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->categoryFactoryMock = $this->getMockBuilder(CategoryFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $categoryMock = $this->getMockBuilder(CategoryInterface::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'create', + 'setName', + 'setPath', + 'setLevel', + 'setAvailableSortBy', + 'setDefaultSortBy', + 'setIsActive', + 'save' + ] + ) + ->getMockForAbstractClass(); + + $this->categoryFactoryMock->expects($this->exactly(5)) + ->method('create') + ->willReturn($categoryMock); - $categoryMock = $this->getMock( - \Magento\Catalog\Model\Category::class, - [ - 'setName', - 'setPath', - 'setLevel', - 'setAvailableSortBy', - 'setDefaultSortBy', - 'setIsActive', - 'getId', - 'save', - 'load' - ], - [], - '', - false - ); - $categoryMock->expects($this->once()) + $categoryMock->expects($this->exactly(5)) ->method('setName') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('setPath') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('setLevel') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('setAvailableSortBy') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('setDefaultSortBy') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('setIsActive') - ->willReturnSelf(); - $categoryMock->expects($this->once()) + ->willReturn($categoryMock); + + $categoryMock->expects($this->exactly(5)) ->method('getId') - ->willReturn('category_id'); + ->willReturn($categoryMock); - $valueMap = [ - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock], - [\Magento\Catalog\Model\Category::class, [], $categoryMock] - ]; + $categoryMock->expects($this->exactly(5)) + ->method('save') + ->willReturn($categoryMock); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) - ->method('create') - ->will($this->returnValueMap($valueMap)); + $this->localeConfigMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getAllowedLocales']) + ->getMock(); - $this->fixtureModelMock - ->expects($this->exactly(3)) - ->method('getValue') - ->will($this->returnValue(1)); - $this->fixtureModelMock - ->expects($this->exactly(2)) - ->method('getObjectManager') - ->willReturn($objectManagerMock); + $this->localeConfigMock->expects($this->once()) + ->method('getAllowedLocales') + ->willReturn(['en_US']); - $this->model->execute(); - } + $this->scopeConfigMock = $this->getMockBuilder(Writer::class) + ->disableOriginalConstructor() + ->getMock(); - public function testNoFixtureConfigValue() - { - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); - $storeMock->expects($this->never())->method('save'); + $storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'getRootCategoryId', 'addData', 'save']) + ->getMockForAbstractClass(); - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); - $storeManagerMock->expects($this->never()) - ->method('getDefaultStoreView') + $storeMock->expects($this->exactly(11)) + ->method('getId') + ->willReturn(1); + + $storeMock->expects($this->exactly(11)) + ->method('addData') ->willReturn($storeMock); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->never()) - ->method('create') - ->with($this->equalTo(\Magento\Store\Model\StoreManager::class)) - ->willReturn($storeManagerMock); + $storeGroupMock = $this->getMockBuilder(GroupInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'addData', 'save']) + ->getMockForAbstractClass(); + + $storeGroupMock->expects($this->exactly(11)) + ->method('getId') + ->willReturn(1); + + $storeGroupMock->expects($this->exactly(5)) + ->method('addData') + ->willReturn($storeGroupMock); + + $websiteMock = $this->getMockBuilder(WebsiteInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'addData', 'save']) + ->getMockForAbstractClass(); + + $websiteMock->expects($this->exactly(3)) + ->method('getId') + ->willReturn(1); + + $websiteMock->expects($this->exactly(2)) + ->method('addData') + ->willReturn($storeGroupMock); + + $this->storeManagerMock->expects($this->once()) + ->method('reinitStores') + ->willReturn('void'); + + $this->storeManagerMock->expects($this->once()) + ->method('getGroups') + ->willReturn([$storeGroupMock]); + + $this->storeManagerMock->expects($this->once()) + ->method('getGroup') + ->willReturn($storeGroupMock); + + $this->storeManagerMock->expects($this->once()) + ->method('getWebsite') + ->willReturn($websiteMock); + + $this->storeManagerMock->expects($this->once()) + ->method('getStores') + ->willReturn([$storeMock]); + + $this->storeManagerMock->expects($this->once()) + ->method('getDefaultStoreView') + ->willReturn($storeMock); $this->fixtureModelMock - ->expects($this->never()) - ->method('getObjectManager') - ->willReturn($objectManagerMock); - $this->fixtureModelMock - ->expects($this->exactly(3)) + ->expects($this->exactly(4)) ->method('getValue') - ->willReturn(false); + ->will($this->returnValueMap( + [ + ['websites', 1, 3], + ['store_groups', 1, 6], + ['store_views', 1, 12], + ['assign_entities_to_all_websites', false] + ] + )); + + $this->model = new StoresFixture( + $this->fixtureModelMock, + $this->storeManagerMock, + $this->eventManagerMock, + $this->categoryFactoryMock, + $this->localeConfigMock, + $this->scopeConfigMock + ); $this->model->execute(); } - - public function testGetActionTitle() - { - $this->assertSame('Generating websites, stores and store views', $this->model->getActionTitle()); - } - - public function testIntroduceParamLabels() - { - $this->assertSame([ - 'websites' => 'Websites', - 'store_groups' => 'Store Groups', - 'store_views' => 'Store Views' - ], $this->model->introduceParamLabels()); - } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/TaxRatesFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/TaxRatesFixtureTest.php index f7a4cc777b334..9e4ef50e33151 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/TaxRatesFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/TaxRatesFixtureTest.php @@ -1,6 +1,6 @@ fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->taxRateFactoryMock = $this->getMockBuilder(TaxRateInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->taxRateRepositoryMock = $this->getMockBuilder(TaxRateRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(ConfigWriter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->taxRuleFactoryMock = $this->getMockBuilder(TaxRuleInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->taxRuleRepositoryMock = $this->getMockBuilder(TaxRuleRepositoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['save', 'get', 'delete', 'deleteById', 'getList']) + ->getMock(); + + $this->fixtureModelMock + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValueMap( + [ + ['tax_mode', 'VAT'], + ['tax_rules', 2] + ] + )); + + $this->taxRateCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $taxRateCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->setMethods(['getAllIds']) + ->getMock(); + + $this->taxRateCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($taxRateCollectionMock); + + $taxRateCollectionMock->expects($this->once()) + ->method('getAllIds') + ->willReturn([1]); + + $this->model = new TaxRulesFixture( + $this->fixtureModelMock, + $this->taxRuleRepositoryMock, + $this->taxRuleFactoryMock, + $this->taxRateCollectionFactoryMock, + $this->taxRateFactoryMock, + $this->taxRateRepositoryMock, + $this->configWriterMock + ); + + $this->model->execute(); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Address/AddressDataGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Address/AddressDataGeneratorTest.php new file mode 100644 index 0000000000000..68825ae774452 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Address/AddressDataGeneratorTest.php @@ -0,0 +1,46 @@ +addressGenerator = new \Magento\Setup\Model\Address\AddressDataGenerator(); + } + + public function testPostcode() + { + mt_srand(42); + $address1 = $this->addressGenerator->generateAddress(); + + mt_srand(66); + $address2 = $this->addressGenerator->generateAddress(); + + $this->assertNotEquals($address1['postcode'], $address2['postcode']); + } + + public function testAddressStructure() + { + $address = $this->addressGenerator->generateAddress(); + + foreach ($this->addressStructure as $addressField) { + $this->assertTrue(array_key_exists($addressField, $address)); + } + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php index 79f393ff6cc1a..f1ea8d843f8ee 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php @@ -1,6 +1,6 @@ '%s', 'name' => 'Static', - 'calculated' => function ($index) { - return $index * 10; + 'calculated' => function ($index, $generatedKey) { + return $index * 10 + $generatedKey; }, ], [ @@ -53,7 +53,7 @@ public function patternDataProvider() 'name' => 'yyy %s' ], ], - 'ecpectedCount' => 3, + 'expectedCount' => 3, 'expectedRowsResult' => [ ['id' => '1', 'name' => 'Static', 'calculated' => 10], ['id' => '', 'name' => 'xxx 1', 'calculated' => ''], @@ -68,7 +68,7 @@ public function patternDataProvider() 'calculated' => 'calc %s', ], ], - 'ecpectedCount' => 1, + 'expectedCount' => 1, 'expectedRowsResult' => [ ['id' => '1', 'name' => 'Dynamic 1', 'calculated' => 'calc 1'], ], diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php index 033ab78c69cd0..6c5b7ce339613 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php @@ -1,6 +1,6 @@ assertSame('Encryption key', $options[0]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\SelectConfigOption::class, $options[1]); $this->assertSame('Session save handler', $options[1]->getDescription()); - $this->assertInstanceOf(\Magento\Framework\Setup\Option\SelectConfigOption::class, $options[2]); - $this->assertSame('Type of definitions used by Object Manager', $options[2]->getDescription()); + $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[2]); + $this->assertSame('Database server host', $options[2]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[3]); - $this->assertSame('Database server host', $options[3]->getDescription()); + $this->assertSame('Database name', $options[3]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[4]); - $this->assertSame('Database name', $options[4]->getDescription()); + $this->assertSame('Database server username', $options[4]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[5]); - $this->assertSame('Database server username', $options[5]->getDescription()); + $this->assertSame('Database server engine', $options[5]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[6]); - $this->assertSame('Database server engine', $options[6]->getDescription()); + $this->assertSame('Database server password', $options[6]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[7]); - $this->assertSame('Database server password', $options[7]->getDescription()); + $this->assertSame('Database table prefix', $options[7]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[8]); - $this->assertSame('Database table prefix', $options[8]->getDescription()); + $this->assertSame('Database type', $options[8]->getDescription()); $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[9]); - $this->assertSame('Database type', $options[9]->getDescription()); - $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[10]); - $this->assertSame('Database initial set of commands', $options[10]->getDescription()); - $this->assertInstanceOf(\Magento\Framework\Setup\Option\FlagConfigOption::class, $options[11]); + $this->assertSame('Database initial set of commands', $options[9]->getDescription()); + $this->assertInstanceOf(\Magento\Framework\Setup\Option\FlagConfigOption::class, $options[10]); $this->assertSame( 'If specified, then db connection validation will be skipped', - $options[11]->getDescription() + $options[10]->getDescription() ); - $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[12]); - $this->assertSame('http Cache hosts', $options[12]->getDescription()); - $this->assertEquals(13, count($options)); + $this->assertInstanceOf(\Magento\Framework\Setup\Option\TextConfigOption::class, $options[11]); + $this->assertSame('http Cache hosts', $options[11]->getDescription()); + $this->assertEquals(12, count($options)); } public function testCreateOptions() diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Cron/Helper/ModuleUninstallTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Cron/Helper/ModuleUninstallTest.php index 4fe96a37c724a..143e0b4e8c1da 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/Cron/Helper/ModuleUninstallTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/Cron/Helper/ModuleUninstallTest.php @@ -1,6 +1,6 @@ getMock(\Magento\Setup\Model\ObjectManagerProvider::class, [], [], '', false); $objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class, [], '', false); @@ -62,18 +66,16 @@ public function testSetCache($commandClass, $arrayInput, $jobName, $params) */ public function setCacheDataProvider() { - $cacheEnable = new ArrayInput(['command' => 'cache:enable', 'types' => ['cache1']]); - $cacheDisable = new ArrayInput(['command' => 'cache:disable']); return [ [ \Magento\Backend\Console\Command\CacheEnableCommand::class, - $cacheEnable, + ['command' => 'cache:enable', 'types' => ['cache1']], 'setup:cache:enable', ['cache1'] ], [ \Magento\Backend\Console\Command\CacheDisableCommand::class, - $cacheDisable, + ['command' => 'cache:disable'], 'setup:cache:disable', [] ], diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobSetMaintenanceModeTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobSetMaintenanceModeTest.php index 450b9a44fcd81..141c59acd92cd 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobSetMaintenanceModeTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobSetMaintenanceModeTest.php @@ -1,6 +1,6 @@ 10 + ]; + + /** + * @var \Magento\Setup\Model\Address\AddressDataGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressGeneratorMock; + + /** + * @var \Magento\Setup\Model\Customer\CustomerDataGenerator + */ + private $customerGenerator; + + public function setUp() + { + $this->groupCollectionFactoryMock = $this->getMock( + \Magento\Customer\Model\ResourceModel\Group\CollectionFactory::class, + ['create', 'getAllIds'], + [], + '', + false + ); + + $this->groupCollectionFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->groupCollectionFactoryMock); + + $this->groupCollectionFactoryMock + ->expects($this->once()) + ->method('getAllIds') + ->willReturn([1]); + + $this->addressGeneratorMock = $this->getMock( + \Magento\Setup\Model\Address\AddressDataGenerator::class, + [], + [], + '', + false + ); + + $this->customerGenerator = new \Magento\Setup\Model\Customer\CustomerDataGenerator( + $this->groupCollectionFactoryMock, + $this->addressGeneratorMock, + $this->config + ); + } + + public function testEmail() + { + $customer = $this->customerGenerator->generate(42); + + $this->assertEquals('user_42@example.com', $customer['customer']['email']); + } + + public function testAddressGeneration() + { + $this->addressGeneratorMock + ->expects($this->exactly(10)) + ->method('generateAddress'); + + $customer = $this->customerGenerator->generate(42); + + $this->assertCount($this->config['addresses-count'], $customer['addresses']); + } + + public function testCustomerGroup() + { + $customer = $this->customerGenerator->generate(1); + $this->assertEquals(1, $customer['customer']['group_id']); + } + + public function testCustomerStructure() + { + $customer = $this->customerGenerator->generate(42); + + foreach ($this->customerStructure as $customerField) { + $this->assertTrue(array_key_exists($customerField, $customer)); + } + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php new file mode 100644 index 0000000000000..4da979497ea54 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php @@ -0,0 +1,50 @@ +generate($wordCount, $wordCount); + + $found = false; + foreach ($data as $word) { + $found = (strpos($result, $word[0]) !== false) || $found; + } + $this->assertTrue($found); + $this->assertEquals($wordCount, count(explode(" ", $result))); + } + + public function testGenerateWithKey() + { + $key = 'generate-test'; + + $data = file(__DIR__ . self::PATH_TO_CSV_FILE); + $wordCount = mt_rand(1, count($data)); + $model = new DataGenerator(__DIR__ . self::PATH_TO_CSV_FILE); + $result = $model->generate($wordCount, $wordCount, $key); + + $foundResult = $model->generate($wordCount, $wordCount, $key); + + $this->assertEquals($wordCount, count(explode(" ", $result))); + $this->assertEquals($result, $foundResult); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/DateTime/DateTimeProviderTest.php b/setup/src/Magento/Setup/Test/Unit/Model/DateTime/DateTimeProviderTest.php index 05992d69495f3..ffde07483b848 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/DateTime/DateTimeProviderTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/DateTime/DateTimeProviderTest.php @@ -1,6 +1,6 @@ [ + 'count-min' => 3, + 'count-max' => 3 + ], + 'mixin' => [ + 'tags' => ['p', 'b', 'div'] + ] + ]; + + /** + * @var array + */ + private $descriptionConfigWithoutMixin = [ + 'paragraphs' => [ + 'count-min' => 3, + 'count-max' => 3 + ] + ]; + + public function setUp() + { + $this->descriptionParagraphGeneratorMock = $this->getMock( + \Magento\Setup\Model\Description\DescriptionParagraphGenerator::class, + [], + [], + '', + false + ); + $this->descriptionParagraphGeneratorMock + ->expects($this->exactly(3)) + ->method('generate') + ->will($this->onConsecutiveCalls( + $this->paragraphs[0], + $this->paragraphs[1], + $this->paragraphs[2] + )); + + $this->mixinManagerMock = $this->getMock( + \Magento\Setup\Model\Description\MixinManager::class, + [], + [], + '', + false + ); + } + + public function testGeneratorWithMixin() + { + $descriptionWithMixin = 'Some description with mixin'; + $this->mixinManagerMock + ->expects($this->once()) + ->method('apply') + ->with( + implode(PHP_EOL, $this->paragraphs), + $this->descriptionConfigWithMixin['mixin']['tags'] + ) + ->willReturn($descriptionWithMixin); + + $generator = new \Magento\Setup\Model\Description\DescriptionGenerator( + $this->descriptionParagraphGeneratorMock, + $this->mixinManagerMock, + $this->descriptionConfigWithMixin + ); + + $this->assertEquals($descriptionWithMixin, $generator->generate()); + } + + public function testGeneratorWithoutMixin() + { + $generator = new \Magento\Setup\Model\Description\DescriptionGenerator( + $this->descriptionParagraphGeneratorMock, + $this->mixinManagerMock, + $this->descriptionConfigWithoutMixin + ); + + $this->assertEquals(implode(PHP_EOL, $this->paragraphs), $generator->generate()); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionParagraphGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionParagraphGeneratorTest.php new file mode 100644 index 0000000000000..7ece974831ed8 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionParagraphGeneratorTest.php @@ -0,0 +1,74 @@ + [ + 'count-min' => 4, + 'count-max' => 4 + ] + ]; + + public function setUp() + { + $this->sentenceGeneratorMock = $this->getMock( + \Magento\Setup\Model\Description\DescriptionSentenceGenerator::class, + [], + [], + '', + false + ); + $this->paragraphGenerator = new \Magento\Setup\Model\Description\DescriptionParagraphGenerator( + $this->sentenceGeneratorMock, + $this->paragraphConfig + ); + } + + /** + * + */ + public function testParagraphGeneration() + { + // @codingStandardsIgnoreStart + $consecutiveSentences = [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + ]; + // @codingStandardsIgnoreEnd + + $this->sentenceGeneratorMock + ->expects($this->exactly(4)) + ->method('generate') + ->will($this->onConsecutiveCalls( + $consecutiveSentences[0], + $consecutiveSentences[1], + $consecutiveSentences[2], + $consecutiveSentences[3] + )); + + $this->assertEquals( + implode(' ', $consecutiveSentences), + $this->paragraphGenerator->generate() + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionSentenceGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionSentenceGeneratorTest.php new file mode 100644 index 0000000000000..7246bb60523a2 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/DescriptionSentenceGeneratorTest.php @@ -0,0 +1,59 @@ + [ + 'count-min' => 7, + 'count-max' => 7 + ] + ]; + + public function setUp() + { + $this->dictionaryMock = $this->getMock(\Magento\Setup\Model\Dictionary::class, [], [], '', false); + $this->sentenceGenerator = new \Magento\Setup\Model\Description\DescriptionSentenceGenerator( + $this->dictionaryMock, + $this->sentenceConfig + ); + } + + public function testSentenceGeneration() + { + $this->dictionaryMock + ->expects($this->exactly(7)) + ->method('getRandWord') + ->will($this->onConsecutiveCalls( + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + 'consectetur', + 'adipiscing' + )); + + $this->assertEquals( + 'Lorem ipsum dolor sit amet consectetur adipiscing.', + $this->sentenceGenerator->generate() + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BoldMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BoldMixinTest.php new file mode 100644 index 0000000000000..3d00ad2d300da --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BoldMixinTest.php @@ -0,0 +1,73 @@ +randomWordSelectorMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\RandomWordSelector::class, + [], + [], + '', + false + ); + $this->wordWrapperMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\WordWrapper::class, + [], + [], + '', + false + ); + + $this->mixin = new \Magento\Setup\Model\Description\Mixin\BoldMixin( + $this->randomWordSelectorMock, + $this->wordWrapperMock + ); + } + + public function testEmptyApply() + { + $this->assertEquals('', $this->mixin->apply('')); + } + + public function testApply() + { + $fixtureString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $fixtureStringResult = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $randWordsFixture = ['Lorem', 'dolor']; + + $this->randomWordSelectorMock + ->expects($this->once()) + ->method('getRandomWords') + ->with($this->equalTo($fixtureString), $this->greaterThan(0)) + ->willReturn($randWordsFixture); + + $this->wordWrapperMock + ->expects($this->once()) + ->method('wrapWords') + ->with($fixtureString, $randWordsFixture, '%s') + ->willReturn($fixtureStringResult); + + $this->assertEquals($fixtureStringResult, $this->mixin->apply($fixtureString)); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BrakeMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BrakeMixinTest.php new file mode 100644 index 0000000000000..873ac70cfbb94 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/BrakeMixinTest.php @@ -0,0 +1,45 @@ +mixin = new \Magento\Setup\Model\Description\Mixin\BrakeMixin(); + } + + /** + * @dataProvider getTestData + */ + public function testApply($subject, $expectedResult) + { + $this->assertEquals($expectedResult, $this->mixin->apply($subject)); + } + + public function getTestData() + { + return [ + ['', ''], + [ + 'Lorem ipsum dolor sit amet.' . PHP_EOL + . 'Consectetur adipiscing elit.' . PHP_EOL + . 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + + 'Lorem ipsum dolor sit amet.' . PHP_EOL + . '
    ' . PHP_EOL + . 'Consectetur adipiscing elit.' . PHP_EOL + . '
    ' . PHP_EOL + . 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' + ] + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/HeaderMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/HeaderMixinTest.php new file mode 100644 index 0000000000000..d017238c1ac81 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/HeaderMixinTest.php @@ -0,0 +1,46 @@ +mixin = new \Magento\Setup\Model\Description\Mixin\HeaderMixin(); + } + + /** + * @dataProvider getTestData + */ + public function testApply($subject, $expectedResult) + { + $this->assertEquals($expectedResult, $this->mixin->apply($subject)); + } + + public function getTestData() + { + return [ + ['', ''], + [ + 'Lorem ipsum dolor sit amet.' . PHP_EOL + . 'Consectetur adipiscing elit.' . PHP_EOL + . 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + + '

    Lorem ipsum

    ' . PHP_EOL + . 'Lorem ipsum dolor sit amet.' . PHP_EOL + . '

    Consectetur

    ' . PHP_EOL + . 'Consectetur adipiscing elit.' . PHP_EOL + . '

    Sed do eiusmod

    ' . PHP_EOL + . 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' + ] + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/RandomWordSelectorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/RandomWordSelectorTest.php new file mode 100644 index 0000000000000..17033c410bba3 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/RandomWordSelectorTest.php @@ -0,0 +1,60 @@ +helper = new \Magento\Setup\Model\Description\Mixin\Helper\RandomWordSelector(); + } + + /** + * @param string $fixtureSource + * @param int $fixtureCount + * @dataProvider getTestData + */ + public function testRandomSelector($fixtureSource, $fixtureCount) + { + $randWords = $this->helper->getRandomWords($fixtureSource, $fixtureCount); + + $this->assertCount($fixtureCount, $randWords); + + $fixtureWords = str_word_count($fixtureSource, 1); + foreach ($randWords as $randWord) { + $this->assertTrue(in_array($randWord, $fixtureWords)); + } + } + + public function getTestData() + { + return [ + [ + 'source' => ' + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + ', + 'count' => 1 + ], + [ + 'source' => 'Lorem.', + 'count' => 5 + ], + [ + 'source' => ' + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + ', + 'count' => 3 + ], + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/WordWrapperTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/WordWrapperTest.php new file mode 100644 index 0000000000000..a31b720bf10c1 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/Helper/WordWrapperTest.php @@ -0,0 +1,64 @@ +wrapper = new \Magento\Setup\Model\Description\Mixin\Helper\WordWrapper(); + } + + /** + * @param array $inputData + * @param string $expectedResult + * @dataProvider getTestData + */ + public function testWrapping($inputData, $expectedResult) + { + $this->assertEquals( + $expectedResult, + $this->wrapper->wrapWords($inputData['source'], $inputData['words'], $inputData['format']) + ); + } + + public function getTestData() + { + return [ + [ + [ + 'source' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'words' => [], + 'format' => '', + ], + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + ], + + [ + [ + 'source' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'words' => ['Lorem'], + 'format' => '%s', + ], + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + ], + + [ + [ + 'source' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'words' => ['Lorem', 'consectetur', 'elit'], + 'format' => '%s', + ], + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + ], + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ItalicMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ItalicMixinTest.php new file mode 100644 index 0000000000000..49e9b8ee5cca5 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ItalicMixinTest.php @@ -0,0 +1,73 @@ +randomWordSelectorMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\RandomWordSelector::class, + [], + [], + '', + false + ); + $this->wordWrapperMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\WordWrapper::class, + [], + [], + '', + false + ); + + $this->mixin = new \Magento\Setup\Model\Description\Mixin\ItalicMixin( + $this->randomWordSelectorMock, + $this->wordWrapperMock + ); + } + + public function testEmptyApply() + { + $this->assertEquals('', $this->mixin->apply('')); + } + + public function testApply() + { + $fixtureString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $fixtureStringResult = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $randWordsFixture = ['Lorem', 'dolor']; + + $this->randomWordSelectorMock + ->expects($this->once()) + ->method('getRandomWords') + ->with($this->equalTo($fixtureString), $this->greaterThan(0)) + ->willReturn($randWordsFixture); + + $this->wordWrapperMock + ->expects($this->once()) + ->method('wrapWords') + ->with($fixtureString, $randWordsFixture, '%s') + ->willReturn($fixtureStringResult); + + $this->assertEquals($fixtureStringResult, $this->mixin->apply($fixtureString)); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ParagraphMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ParagraphMixinTest.php new file mode 100644 index 0000000000000..44cb2433bb62c --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/ParagraphMixinTest.php @@ -0,0 +1,43 @@ +mixin = new \Magento\Setup\Model\Description\Mixin\ParagraphMixin(); + } + + /** + * @dataProvider getTestData + */ + public function testApply($subject, $expectedResult) + { + $this->assertEquals($expectedResult, $this->mixin->apply($subject)); + } + + public function getTestData() + { + return [ + ['', '

    '], + [ + 'Lorem ipsum dolor sit amet.' . PHP_EOL + . 'Consectetur adipiscing elit.' . PHP_EOL + . 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + + '

    Lorem ipsum dolor sit amet.

    ' . PHP_EOL + . '

    Consectetur adipiscing elit.

    ' . PHP_EOL + . '

    Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

    ' + ] + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/SpanMixinTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/SpanMixinTest.php new file mode 100644 index 0000000000000..0376b8554a0b9 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/Mixin/SpanMixinTest.php @@ -0,0 +1,73 @@ +randomWordSelectorMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\RandomWordSelector::class, + [], + [], + '', + false + ); + $this->wordWrapperMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\Helper\WordWrapper::class, + [], + [], + '', + false + ); + + $this->mixin = new \Magento\Setup\Model\Description\Mixin\SpanMixin( + $this->randomWordSelectorMock, + $this->wordWrapperMock + ); + } + + public function testEmptyApply() + { + $this->assertEquals('', $this->mixin->apply('')); + } + + public function testApply() + { + $fixtureString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $fixtureStringResult = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + $randWordsFixture = ['Lorem', 'dolor']; + + $this->randomWordSelectorMock + ->expects($this->once()) + ->method('getRandomWords') + ->with($this->equalTo($fixtureString), $this->greaterThan(0)) + ->willReturn($randWordsFixture); + + $this->wordWrapperMock + ->expects($this->once()) + ->method('wrapWords') + ->with($fixtureString, $randWordsFixture, '%s') + ->willReturn($fixtureStringResult); + + $this->assertEquals($fixtureStringResult, $this->mixin->apply($fixtureString)); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Description/MixinManagerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Description/MixinManagerTest.php new file mode 100644 index 0000000000000..e9a57559de783 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/Description/MixinManagerTest.php @@ -0,0 +1,82 @@ +mixinFactoryMock = $this->getMock( + \Magento\Setup\Model\Description\Mixin\MixinFactory::class, + [], + [], + '', + false + ); + $this->mixinManager = new \Magento\Setup\Model\Description\MixinManager($this->mixinFactoryMock); + } + + public function testApply() + { + $description = '>o<'; + $mixinList = ['x', 'y', 'z']; + + $xMixinMock = $this->getMockForAbstractClass( + \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface::class + ); + $xMixinMock->expects($this->once()) + ->method('apply') + ->with($description) + ->willReturn($description . 'x'); + + $yMixinMock = $this->getMockForAbstractClass( + \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface::class + ); + $yMixinMock->expects($this->once()) + ->method('apply') + ->with($description . 'x') + ->willReturn($description . 'xy'); + + $zMixinMock = $this->getMockForAbstractClass( + \Magento\Setup\Model\Description\Mixin\DescriptionMixinInterface::class + ); + $zMixinMock->expects($this->once()) + ->method('apply') + ->with($description . 'xy') + ->willReturn($description . 'xyz'); + + $this->mixinFactoryMock + ->expects($this->exactly(count($mixinList))) + ->method('create') + ->withConsecutive( + [$mixinList[0]], + [$mixinList[1]], + [$mixinList[2]] + ) + ->will( + $this->onConsecutiveCalls( + $xMixinMock, + $yMixinMock, + $zMixinMock + ) + ); + + $this->assertEquals( + $description . 'xyz', + $this->mixinManager->apply($description, $mixinList) + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/DictionaryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/DictionaryTest.php new file mode 100644 index 0000000000000..158c8a9736aaf --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/DictionaryTest.php @@ -0,0 +1,59 @@ +getRandWord(); + } + + /** + * @expectedException \Magento\Setup\Exception + * @expectedExceptionMessageRegExp /Dictionary file .*empty-dictionary\.csv is empty/ + */ + public function testDictionaryFileIsEmptyException() + { + $filePath = __DIR__ . '/_files/empty-dictionary.csv'; + file_put_contents($filePath, ''); + + try { + $dictionary = new \Magento\Setup\Model\Dictionary($filePath); + $dictionary->getRandWord(); + } finally { + unlink($filePath); + } + } + + public function testGetRandWord() + { + $filePath = __DIR__ . '/_files/valid-dictionary.csv'; + file_put_contents($filePath, implode(PHP_EOL, $this->dictionary)); + + $dictionary = new \Magento\Setup\Model\Dictionary($filePath); + + $this->assertTrue(in_array($dictionary->getRandWord(), $this->dictionary)); + $this->assertTrue(in_array($dictionary->getRandWord(), $this->dictionary)); + $this->assertTrue(in_array($dictionary->getRandWord(), $this->dictionary)); + + unlink($filePath); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/FixtureGenerator/SqlCollectorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/FixtureGenerator/SqlCollectorTest.php new file mode 100644 index 0000000000000..617743a047ef3 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/FixtureGenerator/SqlCollectorTest.php @@ -0,0 +1,123 @@ +resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->unit = (new ObjectManager($this))->getObject( + SqlCollector::class, + ['resourceConnection' => $this->resourceConnection] + ); + } + + public function testGetEmptySql() + { + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->setMethods(['getProfiler']) + ->getMockForAbstractClass(); + $profiler = $this->getMockBuilder(\Zend_Db_Profiler::class) + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->once())->method('getProfiler')->willReturn($profiler); + $this->resourceConnection->expects($this->once())->method('getConnection')->willReturn($connection); + + $profiler->expects($this->once())->method('getQueryProfiles')->willReturn([]); + + $this->unit->disable(); + $this->assertEquals([], $this->unit->getSql()); + } + + public function testGetEmptySqlWhenSelectQueryProcessed() + { + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->setMethods(['getProfiler']) + ->getMockForAbstractClass(); + $profiler = $this->getMockBuilder(\Zend_Db_Profiler::class) + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->once())->method('getProfiler')->willReturn($profiler); + $this->resourceConnection->expects($this->once())->method('getConnection')->willReturn($connection); + + $query = $this->getMockBuilder(\Zend_Db_Profiler_Query::class)->disableOriginalConstructor()->getMock(); + $query->expects($this->once())->method('getQueryType')->willReturn(\Zend_Db_Profiler::SELECT); + $profiler->expects($this->once())->method('getQueryProfiles')->willReturn([$query]); + + $this->unit->disable(); + $this->assertEquals([], $this->unit->getSql()); + } + + public function testGetSql() + { + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->setMethods(['getProfiler']) + ->getMockForAbstractClass(); + $profiler = $this->getMockBuilder(\Zend_Db_Profiler::class) + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->once())->method('getProfiler')->willReturn($profiler); + $this->resourceConnection->expects($this->once())->method('getConnection')->willReturn($connection); + + $query = $this->getMockBuilder(\Zend_Db_Profiler_Query::class)->disableOriginalConstructor()->getMock(); + $query->expects($this->once())->method('getQueryType')->willReturn(\Zend_Db_Profiler::INSERT); + $query->expects($this->once())->method('getQuery')->willReturn( + 'INSERT INTO `catalog_product_entity` (id, sku, type, created_at, attribute_set)' + . ' VALUES (?, ?, ?, \'2013-12-11\', ?), (?, ?, ?, \'2013-12-11\', ?)' + ); + $query->expects($this->once())->method('getQueryParams')->willReturn([ + 4, 'sku_4', 'simple', 4, 5, 'sku_5', 'simple', 12 + ]); + $profiler->expects($this->once())->method('getQueryProfiles')->willReturn([$query]); + + $this->unit->disable(); + $this->assertEquals( + [ + [ + [ + [ + 'id' => 4, + 'sku' => 'sku_4', + 'type' => 'simple', + 'created_at' => '2013-12-11', + 'attribute_set' => 4, + ], + [ + 'id' => 5, + 'sku' => 'sku_5', + 'type' => 'simple', + 'created_at' => '2013-12-11', + 'attribute_set' => 12, + ], + ], + 'catalog_product_entity' + ] + ], + $this->unit->getSql() + ); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/GeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/GeneratorTest.php index 670fa5420699e..5e71e1d37f94a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/GeneratorTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/GeneratorTest.php @@ -1,6 +1,6 @@ getComposerInformation(); - $timeZoneProvider = $this->getMock(\Magento\Setup\Model\DateTime\TimeZoneProvider::class, [], [], '', false); + $this->composerInformation = $this->getComposerInformation(); + $this->timeZoneProvider = $this->getMockBuilder(\Magento\Setup\Model\DateTime\TimeZoneProvider::class) + ->disableOriginalConstructor() + ->getMock(); $timeZone = $this->getMock(\Magento\Framework\Stdlib\DateTime\Timezone::class, [], [], '', false); - $timeZoneProvider->expects($this->any())->method('get')->willReturn($timeZone); - $packagesAuth = $this->getMock(\Magento\Setup\Model\PackagesAuth::class, [], [], '', false); - $filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); - $objectManagerProvider = $this->getMock(\Magento\Setup\Model\ObjectManagerProvider::class, [], [], '', false); + $this->timeZoneProvider->expects($this->any())->method('get')->willReturn($timeZone); + $this->packagesAuth = $this->getMock(\Magento\Setup\Model\PackagesAuth::class, [], [], '', false); + $this->filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); + $this->objectManagerProvider = $this->getMockBuilder(\Magento\Setup\Model\ObjectManagerProvider::class) + ->disableOriginalConstructor() + ->getMock(); $objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $applicationFactory = $this->getMock( - \Magento\Framework\Composer\MagentoComposerApplicationFactory::class, - [], - [], - '', - false - ); + $appFactory = $this->getMockBuilder(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class) + ->disableOriginalConstructor() + ->getMock(); $application = $this->getMock(\Magento\Composer\MagentoComposerApplication::class, [], [], '', false); $application->expects($this->any()) ->method('runComposerCommand') - ->willReturn('versions: 2.0.1'); - $applicationFactory->expects($this->any())->method('create')->willReturn($application); + ->willReturnMap([ + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'magento/package-1', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 2.0.1' + ], + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'magento/package-2', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 2.0.1' + ], + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'partner/package-3', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 3.0.1' + ], + ]); + $appFactory->expects($this->any())->method('create')->willReturn($application); $objectManager->expects($this->any()) ->method('get') ->with(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class) - ->willReturn($applicationFactory); - $objectManagerProvider->expects($this->any())->method('get')->willReturn($objectManager); + ->willReturn($appFactory); + $this->objectManagerProvider->expects($this->any())->method('get')->willReturn($objectManager); $directoryWrite = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\WriteInterface::class); $directoryRead = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $filesystem->expects($this->any())->method('getDirectoryRead')->will($this->returnValue($directoryRead)); - $filesystem->expects($this->any()) + $this->filesystem->expects($this->any())->method('getDirectoryRead')->will($this->returnValue($directoryRead)); + $this->filesystem->expects($this->any()) ->method('getDirectoryWrite') ->will($this->returnValue($directoryWrite)); $directoryWrite->expects($this->any())->method('isExist')->willReturn(true); @@ -81,32 +139,41 @@ public function setUp() . '}}}' ); - $typeMapper = $this->getMockBuilder(\Magento\Setup\Model\Grid\TypeMapper::class) + $this->typeMapper = $this->getMockBuilder(\Magento\Setup\Model\Grid\TypeMapper::class) ->disableOriginalConstructor() ->getMock(); - $typeMapper->expects(static::any()) + $this->typeMapper->expects(static::any()) ->method('map') ->willReturnMap([ [ComposerInformation::MODULE_PACKAGE_TYPE, \Magento\Setup\Model\Grid\TypeMapper::MODULE_PACKAGE_TYPE], ]); + $this->createPackagesData(); + } + + private function createPackagesData() + { $this->packagesData = new PackagesData( - $composerInformation, - $timeZoneProvider, - $packagesAuth, - $filesystem, - $objectManagerProvider, - $typeMapper + $this->composerInformation, + $this->timeZoneProvider, + $this->packagesAuth, + $this->filesystem, + $this->objectManagerProvider, + $this->typeMapper ); } /** + * @param array $requiredPackages + * @param array $installedPackages + * @param array $repo * @return ComposerInformation|MockObject */ - private function getComposerInformation() + private function getComposerInformation($requiredPackages = [], $installedPackages = [], $repo = []) { $composerInformation = $this->getMock(ComposerInformation::class, [], [], '', false); $composerInformation->expects($this->any())->method('getInstalledMagentoPackages')->willReturn( + $installedPackages ?: [ 'magento/package-1' => [ 'name' => 'magento/package-1', @@ -117,21 +184,30 @@ private function getComposerInformation() 'name' => 'magento/package-2', 'type' => 'magento2-module', 'version'=> '1.0.1' - ] + ], + 'partner/package-3' => [ + 'name' => 'partner/package-3', + 'type' => 'magento2-module', + 'version'=> '3.0.0' + ], ] ); $composerInformation->expects($this->any())->method('getRootRepositories') - ->willReturn(['repo1', 'repo2']); + ->willReturn($repo ?: ['repo1', 'repo2']); $composerInformation->expects($this->any())->method('getPackagesTypes') ->willReturn(['magento2-module']); $rootPackage = $this->getMock(RootPackage::class, [], ['magento/project', '2.1.0', '2']); $rootPackage->expects($this->any()) ->method('getRequires') - ->willReturn([ - 'magento/package-1' => '1.0.0', - 'magento/package-2' => '1.0.1' - ]); + ->willReturn( + $requiredPackages ?: + [ + 'magento/package-1' => '1.0.0', + 'magento/package-2' => '1.0.1', + 'partner/package-3' => '3.0.0', + ] + ); $composerInformation->expects($this->any()) ->method('getRootPackage') ->willReturn($rootPackage); @@ -146,19 +222,57 @@ public function testSyncPackagesData() $this->assertArrayHasKey('date', $latestData['lastSyncDate']); $this->assertArrayHasKey('time', $latestData['lastSyncDate']); $this->assertArrayHasKey('packages', $latestData); - $this->assertSame(2, count($latestData['packages'])); - $this->assertSame(2, $latestData['countOfUpdate']); + $this->assertSame(3, count($latestData['packages'])); + $this->assertSame(3, $latestData['countOfUpdate']); $this->assertArrayHasKey('installPackages', $latestData); $this->assertSame(1, count($latestData['installPackages'])); $this->assertSame(1, $latestData['countOfInstall']); } - public function testGetPackagesForUpdate() + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Couldn't get available versions for package partner/package-4 + */ + public function testGetPackagesForUpdateWithException() { + $requiredPackages = [ + 'partner/package-4' => '4.0.4', + ]; + $installedPackages = [ + 'partner/package-4' => [ + 'name' => 'partner/package-4', + 'type' => 'magento2-module', + 'version'=> '4.0.4' + ], + ]; + $this->composerInformation = $this->getComposerInformation($requiredPackages, $installedPackages); + $this->createPackagesData(); + $this->packagesData->getPackagesForUpdate(); + } + + public function testPackagesForUpdateFromJson() + { + $this->composerInformation = $this->getComposerInformation([], [], ['https://repo1']); + $this->packagesAuth->expects($this->atLeastOnce()) + ->method('getCredentialBaseUrl') + ->willReturn('repo1'); + $this->createPackagesData(); $packages = $this->packagesData->getPackagesForUpdate(); $this->assertEquals(2, count($packages)); $this->assertArrayHasKey('magento/package-1', $packages); + $this->assertArrayHasKey('partner/package-3', $packages); + $firstPackage = array_values($packages)[0]; + $this->assertArrayHasKey('latestVersion', $firstPackage); + $this->assertArrayHasKey('versions', $firstPackage); + } + + public function testGetPackagesForUpdate() + { + $packages = $this->packagesData->getPackagesForUpdate(); + $this->assertEquals(3, count($packages)); + $this->assertArrayHasKey('magento/package-1', $packages); $this->assertArrayHasKey('magento/package-2', $packages); + $this->assertArrayHasKey('partner/package-3', $packages); $firstPackage = array_values($packages)[0]; $this->assertArrayHasKey('latestVersion', $firstPackage); $this->assertArrayHasKey('versions', $firstPackage); @@ -167,9 +281,10 @@ public function testGetPackagesForUpdate() public function testGetInstalledPackages() { $installedPackages = $this->packagesData->getInstalledPackages(); - $this->assertEquals(2, count($installedPackages)); + $this->assertEquals(3, count($installedPackages)); $this->assertArrayHasKey('magento/package-1', $installedPackages); $this->assertArrayHasKey('magento/package-2', $installedPackages); + $this->assertArrayHasKey('partner/package-3', $installedPackages); } public function testGetMetaPackagesMap() diff --git a/setup/src/Magento/Setup/Test/Unit/Model/PayloadValidatorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/PayloadValidatorTest.php index 2b1d8529d7818..27f6ec8151e6b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/PayloadValidatorTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/PayloadValidatorTest.php @@ -1,6 +1,6 @@ descriptionGeneratorMock = $this->getMock( + \Magento\Setup\Model\Description\DescriptionGenerator::class, + [], + [], + '', + false + ); + $this->searchTermManagerMock = $this->getMock( + \Magento\Setup\Model\SearchTermManager::class, + [], + [], + '', + false + ); + + $this->searchTermDescriptionGenerator = new \Magento\Setup\Model\SearchTermDescriptionGenerator( + $this->descriptionGeneratorMock, + $this->searchTermManagerMock + ); + } + + public function testGeneratorWithCaching() + { + $descriptionMock = ''; + $firstProductIndex = 1; + $secondProductIndex = 2; + + $this->descriptionGeneratorMock + ->expects($this->once()) + ->method('generate') + ->willReturn($descriptionMock); + + $this->searchTermManagerMock + ->expects($this->exactly(2)) + ->method('applySearchTermsToDescription') + ->withConsecutive( + [$descriptionMock, $firstProductIndex], + [$descriptionMock, $secondProductIndex] + ); + + $this->searchTermDescriptionGenerator->generate($firstProductIndex); + $this->searchTermDescriptionGenerator->generate($secondProductIndex); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/SearchTermManagerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/SearchTermManagerTest.php new file mode 100644 index 0000000000000..0ca70d36345c1 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/SearchTermManagerTest.php @@ -0,0 +1,76 @@ + 'x-wing', + 'count' => '33' + ], [ + 'term' => 'tie-fighter', + 'count' => '100' + ], [ + 'term' => 'n-1 starfighter', + 'count' => '42' + ], + ]; + + /** + * @var array + */ + private $searchTermsUsage = [ + 'x-wing' => [ + 'used' => 0 + ], + 'tie-fighter' => [ + 'used' => 0 + ], + 'n-1 starfighter' => [ + 'used' => 0 + ] + ]; + + public function setUp() + { + $this->searchTermManager = new \Magento\Setup\Model\SearchTermManager( + $this->searchTermConfiguration, + $this->totalProductsCount + ); + } + + public function testSearchTermApplied() + { + for ($productIndex=1; $productIndex<=$this->totalProductsCount; $productIndex++) { + $description = 'Fleet: '; + $this->searchTermManager->applySearchTermsToDescription($description, $productIndex); + + foreach (array_keys($this->searchTermsUsage) as $searchTerm) { + if (preg_match("/\\b$searchTerm\\b/", $description)) { + $this->searchTermsUsage[$searchTerm]['used']++; + } + } + } + + foreach ($this->searchTermConfiguration as $searchTerm) { + $this->assertEquals($searchTerm['count'], $this->searchTermsUsage[$searchTerm['term']]['used']); + } + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/StoreConfigurationDataMapperTest.php b/setup/src/Magento/Setup/Test/Unit/Model/StoreConfigurationDataMapperTest.php index 67792c93ee71d..902763d8821b9 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/StoreConfigurationDataMapperTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/StoreConfigurationDataMapperTest.php @@ -1,6 +1,6 @@ assertEquals([], $returnValue->getData()); } - public function testCreateDefinitionsConfig() - { - $testData = [ConfigOptionsListConstants::INPUT_KEY_DEFINITION_FORMAT => 'test-format']; - $returnValue = $this->configGeneratorObject->createDefinitionsConfig($testData); - $this->assertEquals(ConfigFilePool::APP_ENV, $returnValue->getFileKey()); - $this->assertEquals(['definition' => ['format' => 'test-format']], $returnValue->getData()); - } - public function testCreateDbConfig() { $testData = [ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php index 8b887ab7ba002..4c21ce39cdfb6 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php @@ -1,6 +1,6 @@ logMock = $this->getMockBuilder(\Magento\Setup\Module\Di\Compiler\Log\Log::class) @@ -66,7 +64,7 @@ protected function setUp() $this->classReaderMock, $this->classesScanner, $this->validatorMock, - '/var/generation' + '/generated/code' ); } @@ -111,7 +109,7 @@ public function testGetList() public function testGetListNoValidation() { - $path = '/var/generation'; + $path = '/generated/code'; $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2']; diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php index 212575e644d7c..16e1595f5d076 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php @@ -1,6 +1,6 @@ logMock = $this->getMockBuilder(\Magento\Setup\Module\Di\Compiler\Log\Log::class) @@ -97,7 +100,7 @@ public function testGetList() public function testGetListNoValidation() { - $path = '/var/generation'; + $path = '/generated/code'; $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2']; diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/ArrayScannerTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/ArrayScannerTest.php index 453396c3981ec..c29c5dd3a50df 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/ArrayScannerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Scanner/ArrayScannerTest.php @@ -1,6 +1,6 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/di.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/di.xml index 47deb25b5070d..0762ca67ec894 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/di.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/di.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/source/PhpExt.php/content b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/source/PhpExt.php/content index 3c2daa452ee8d..af373f0f2eddd 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/source/PhpExt.php/content +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/source/PhpExt.php/content @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/view/frontend/default.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/view/frontend/default.xml index 119ace1d0e356..2dd9e4557d316 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/view/frontend/default.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/view/frontend/default.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/design/adminhtml/default/backend/layout.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/design/adminhtml/default/backend/layout.xml index a8096b9620b85..0a9bcfa548481 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/design/adminhtml/default/backend/layout.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/design/adminhtml/default/backend/layout.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/additional.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/additional.xml index a8096b9620b85..0a9bcfa548481 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/additional.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/additional.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/config.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/config.xml index 20a064f70bc1b..828f16428a58d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/config.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/config.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/di/config.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/di/config.xml index 17af1192e5a70..77251c257c8ee 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/di/config.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/etc/di/config.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/extension_attributes.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/extension_attributes.xml index e79052491cbc4..14e5c0662ef44 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/extension_attributes.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/extension_attributes.xml @@ -1,7 +1,7 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php b/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php index 9bcac1e73ccee..331d464e45785 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php @@ -1,6 +1,6 @@ _handler = STDOUT; - } - public function testThatHandlerIsRight() { - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - /** @var \Magento\Setup\Module\I18n\Dictionary\Writer\Csv $writer */ - $writer = $objectManagerHelper->getObject(\Magento\Setup\Module\I18n\Dictionary\Writer\Csv\Stdo::class); - - $this->assertAttributeEquals($this->_handler, '_fileHandler', $writer); + $handler = STDOUT; + // Mocking object's under test destructor here is perfectly valid as there is no way to reopen STDOUT + $writer = $this->getMock(Stdo::class, ['__destruct']); + $this->assertAttributeEquals($handler, '_fileHandler', $writer); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Dictionary/Writer/CsvTest.php b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Dictionary/Writer/CsvTest.php index 53c0355403c67..28b9aa455413f 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Dictionary/Writer/CsvTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Dictionary/Writer/CsvTest.php @@ -1,6 +1,6 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html index c25db093ecd33..aa894528348f1 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/email.html @@ -1,6 +1,6 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/file.js b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/file.js index 4b7a3cb8df84a..d835e64d630e8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/file.js +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/Adapter/_files/file.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/ParserTest.php b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/ParserTest.php index 162264c39514a..bb031cc7f1339 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/ParserTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/Parser/ParserTest.php @@ -1,6 +1,6 @@ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/_files/files_collector/file.js b/setup/src/Magento/Setup/Test/Unit/Module/I18n/_files/files_collector/file.js index 51ed125d3b1f3..094a9766127a5 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/I18n/_files/files_collector/file.js +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/_files/files_collector/file.js @@ -1,5 +1,5 @@ /** - * Copyright © 2016 Magento. All rights reserved. + * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php index baf7eb3d01a1a..1bd2b7fda4ca3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php @@ -1,6 +1,6 @@ withConsecutive( [ \Magento\Framework\App\Filesystem\DirectoryList::class, - $this->isInstanceOf(\Magento\Framework\App\Filesystem\DirectoryList::class) + $this->isInstanceOf(\Magento\Framework\App\Filesystem\DirectoryList::class), ], [ \Magento\Framework\Filesystem::class, - $this->isInstanceOf(\Magento\Framework\Filesystem::class) + $this->isInstanceOf(\Magento\Framework\Filesystem::class), ] ); $mvcApplication->expects($this->any())->method('getServiceManager')->willReturn($serviceManager); @@ -130,10 +129,10 @@ public function testCreateService($zfAppConfig, $env, $cliParam, $expectedArray) $request->expects($this->any()) ->method('getContent') ->willReturn( - $cliParam ? ['install', '--magento-init-params=' . $cliParam ] : ['install'] + $cliParam ? ['install', '--magento-init-params=' . $cliParam] : ['install'] ); $mvcApplication->expects($this->any())->method('getConfig')->willReturn( - $zfAppConfig ? [InitParamListener::BOOTSTRAP_PARAM => $zfAppConfig]:[] + $zfAppConfig ? [InitParamListener::BOOTSTRAP_PARAM => $zfAppConfig] : [] ); $mvcApplication->expects($this->any())->method('getRequest')->willReturn($request); @@ -150,41 +149,55 @@ public function createServiceDataProvider() 'mage_mode App' => [['MAGE_MODE' => 'developer'], [], '', ['MAGE_MODE' => 'developer']], 'mage_mode Env' => [[], ['MAGE_MODE' => 'developer'], '', ['MAGE_MODE' => 'developer']], 'mage_mode CLI' => [[], [], 'MAGE_MODE=developer', ['MAGE_MODE' => 'developer']], - 'one MAGE_DIRS CLI' => [[], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer']], + 'one MAGE_DIRS CLI' => [ + [], + [], + 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2', + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], + ], 'two MAGE_DIRS CLI' => [ [], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2&MAGE_DIRS[cache][path]=/tmp/cache', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], - 'MAGE_MODE' => 'developer']], + [ + 'MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], + 'MAGE_MODE' => 'developer', + ], + ], 'mage_mode only' => [[], [], 'MAGE_MODE=developer', ['MAGE_MODE' => 'developer']], 'MAGE_DIRS Env' => [ [], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], '', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], + ], 'two MAGE_DIRS' => [ [], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2&MAGE_DIRS[cache][path]=/tmp/cache', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], - 'MAGE_MODE' => 'developer']], + [ + 'MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], + 'MAGE_MODE' => 'developer', + ], + ], 'Env overwrites App' => [ ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/App']], 'MAGE_MODE' => 'developer'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer'], '', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer'], + ], 'CLI overwrites Env' => [ ['MAGE_MODE' => 'developerApp'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']]], 'MAGE_DIRS[base][path]=/var/www/magento2/CLI', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'developerApp']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'developerApp'], + ], 'CLI overwrites All' => [ ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/App']], 'MAGE_MODE' => 'production'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']]], 'MAGE_DIRS[base][path]=/var/www/magento2/CLI', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'production']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'production'], + ], ]; } @@ -226,6 +239,168 @@ private function prepareEventManager() [$this->listener, 'onBootstrap'] )->willReturn($this->callbackHandler); $eventManager->expects($this->once())->method('getSharedManager')->willReturn($sharedManager); + return $eventManager; } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testAuthPreDispatch() + { + $eventMock = $this->getMockBuilder(\Zend\Mvc\MvcEvent::class) + ->disableOriginalConstructor() + ->getMock(); + $routeMatchMock = $this->getMockBuilder(\Zend\Mvc\Router\Http\RouteMatch::class) + ->disableOriginalConstructor() + ->getMock(); + $applicationMock = $this->getMockBuilder(\Zend\Mvc\Application::class) + ->disableOriginalConstructor() + ->getMock(); + $serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + ->disableOriginalConstructor() + ->getMock(); + $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $deploymentConfigMock->expects($this->once()) + ->method('isAvailable') + ->willReturn(true); + $omProvider = $this->getMockBuilder(\Magento\Setup\Model\ObjectManagerProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); + $adminAppStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) + ->disableOriginalConstructor() + ->getMock(); + $sessionConfigMock = $this->getMockBuilder(\Magento\Backend\Model\Session\AdminConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $backendAppListMock = $this->getMockBuilder(\Magento\Backend\App\BackendAppList::class) + ->disableOriginalConstructor() + ->getMock(); + $backendAppMock = $this->getMockBuilder(\Magento\Backend\App\BackendApp::class) + ->disableOriginalConstructor() + ->getMock(); + $backendUrlFactoryMock = $this->getMockBuilder(\Magento\Backend\Model\UrlFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $backendUrlMock = $this->getMockBuilder(\Magento\Backend\Model\Url::class) + ->disableOriginalConstructor() + ->getMock(); + $authenticationMock = $this->getMockBuilder(\Magento\Backend\Model\Auth::class) + ->disableOriginalConstructor() + ->getMock(); + $adminSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) + ->disableOriginalConstructor() + ->getMock(); + $responseMock = $this->getMockBuilder(\Zend\Http\Response::class) + ->disableOriginalConstructor() + ->getMock(); + $headersMock = $this->getMockBuilder(\Zend\Http\Headers::class) + ->disableOriginalConstructor() + ->getMock(); + + $routeMatchMock->expects($this->once()) + ->method('getParam') + ->with('controller') + ->willReturn('testController'); + $eventMock->expects($this->once()) + ->method('getRouteMatch') + ->willReturn($routeMatchMock); + $eventMock->expects($this->once()) + ->method('getApplication') + ->willReturn($applicationMock); + $serviceManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap( + [ + [ + \Magento\Framework\App\DeploymentConfig::class, + true, + $deploymentConfigMock, + ], + [ + \Magento\Setup\Model\ObjectManagerProvider::class, + true, + $omProvider, + ], + ] + ); + $objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap( + [ + [ + \Magento\Framework\App\State::class, + $adminAppStateMock, + ], + [ + \Magento\Backend\Model\Session\AdminConfig::class, + $sessionConfigMock, + ], + [ + \Magento\Backend\App\BackendAppList::class, + $backendAppListMock, + ], + [ + \Magento\Backend\Model\UrlFactory::class, + $backendUrlFactoryMock, + ], + [ + \Magento\Backend\Model\Auth::class, + $authenticationMock, + ], + ] + ); + $objectManagerMock->expects($this->any()) + ->method('create') + ->willReturn($adminSessionMock); + $omProvider->expects($this->once()) + ->method('get') + ->willReturn($objectManagerMock); + $adminAppStateMock->expects($this->once()) + ->method('setAreaCode') + ->with(\Magento\Framework\App\Area::AREA_ADMINHTML); + $applicationMock->expects($this->once()) + ->method('getServiceManager') + ->willReturn($serviceManagerMock); + $backendAppMock->expects($this->once()) + ->method('getCookiePath') + ->willReturn(''); + $backendUrlFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($backendUrlMock); + $backendAppListMock->expects($this->once()) + ->method('getBackendApp') + ->willReturn($backendAppMock); + $authenticationMock->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(true); + $adminSessionMock->expects($this->once()) + ->method('isAllowed') + ->with('Magento_Backend::setup_wizard', null) + ->willReturn(false); + $adminSessionMock->expects($this->once()) + ->method('destroy'); + $eventMock->expects($this->once()) + ->method('getResponse') + ->willReturn($responseMock); + $responseMock->expects($this->once()) + ->method('getHeaders') + ->willReturn($headersMock); + $headersMock->expects($this->once()) + ->method('addHeaderLine'); + $responseMock->expects($this->once()) + ->method('setStatusCode') + ->with(302); + $eventMock->expects($this->once()) + ->method('stopPropagation'); + + $this->assertSame( + $this->listener->authPreDispatch($eventMock), + $responseMock + ); + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php b/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php index c263531979573..b04b173e652b9 100644 --- a/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/add-database.phtml b/setup/view/magento/setup/add-database.phtml index 6d25db2e08444..3771a66447381 100644 --- a/setup/view/magento/setup/add-database.phtml +++ b/setup/view/magento/setup/add-database.phtml @@ -1,6 +1,6 @@
    -
    \ No newline at end of file +
    diff --git a/setup/view/magento/setup/customize-your-store.phtml b/setup/view/magento/setup/customize-your-store.phtml index e16362721dfde..9cfab20171a0e 100644 --- a/setup/view/magento/setup/customize-your-store.phtml +++ b/setup/view/magento/setup/customize-your-store.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/extension-grid.phtml b/setup/view/magento/setup/extension-grid.phtml index 1096adca72043..45508cfe186ff 100644 --- a/setup/view/magento/setup/extension-grid.phtml +++ b/setup/view/magento/setup/extension-grid.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/home.phtml b/setup/view/magento/setup/home.phtml index f5783f07e0a4a..bd0151f23d69c 100644 --- a/setup/view/magento/setup/home.phtml +++ b/setup/view/magento/setup/home.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/index.phtml b/setup/view/magento/setup/index.phtml index ba9096c5766c6..81e39cb64fd0a 100644 --- a/setup/view/magento/setup/index.phtml +++ b/setup/view/magento/setup/index.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/install-extension-grid.phtml b/setup/view/magento/setup/install-extension-grid.phtml index e2bcbf9a777c8..3cb59b01f9d9c 100644 --- a/setup/view/magento/setup/install-extension-grid.phtml +++ b/setup/view/magento/setup/install-extension-grid.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/install.phtml b/setup/view/magento/setup/install.phtml index 06eb1a082ca66..01785701b0ed5 100644 --- a/setup/view/magento/setup/install.phtml +++ b/setup/view/magento/setup/install.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/marketplace-credentials.phtml b/setup/view/magento/setup/marketplace-credentials.phtml index f5357c781d191..8b7a11c030ec7 100644 --- a/setup/view/magento/setup/marketplace-credentials.phtml +++ b/setup/view/magento/setup/marketplace-credentials.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/module-grid.phtml b/setup/view/magento/setup/module-grid.phtml index 3f4f755ae3f6d..5576f823f1d10 100644 --- a/setup/view/magento/setup/module-grid.phtml +++ b/setup/view/magento/setup/module-grid.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/navigation/header-bar.phtml b/setup/view/magento/setup/navigation/header-bar.phtml index 540bfcfed6359..698ac25346f43 100644 --- a/setup/view/magento/setup/navigation/header-bar.phtml +++ b/setup/view/magento/setup/navigation/header-bar.phtml @@ -1,6 +1,6 @@
    - \ No newline at end of file + diff --git a/setup/view/magento/setup/navigation/menu.phtml b/setup/view/magento/setup/navigation/menu.phtml index ae94a891255dc..b7e5052e4231d 100644 --- a/setup/view/magento/setup/navigation/menu.phtml +++ b/setup/view/magento/setup/navigation/menu.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/readiness-check.phtml b/setup/view/magento/setup/readiness-check.phtml index 2aaf4de649bf0..018e3274f0167 100644 --- a/setup/view/magento/setup/readiness-check.phtml +++ b/setup/view/magento/setup/readiness-check.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/readiness-check/progress.phtml b/setup/view/magento/setup/readiness-check/progress.phtml index 6719d825dfcde..c2d21b911cbee 100755 --- a/setup/view/magento/setup/readiness-check/progress.phtml +++ b/setup/view/magento/setup/readiness-check/progress.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/start-updater.phtml b/setup/view/magento/setup/start-updater.phtml index fd31f994c3923..c4d226ceaab0b 100644 --- a/setup/view/magento/setup/start-updater.phtml +++ b/setup/view/magento/setup/start-updater.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/success.phtml b/setup/view/magento/setup/success.phtml index c220d91e7d058..7931bc670177e 100644 --- a/setup/view/magento/setup/success.phtml +++ b/setup/view/magento/setup/success.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/update-extension-grid.phtml b/setup/view/magento/setup/update-extension-grid.phtml index 62c9b1217f2a5..e26df4d90a2c8 100644 --- a/setup/view/magento/setup/update-extension-grid.phtml +++ b/setup/view/magento/setup/update-extension-grid.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/updater-success.phtml b/setup/view/magento/setup/updater-success.phtml index 07855edcaafc7..da91d44ef1efd 100644 --- a/setup/view/magento/setup/updater-success.phtml +++ b/setup/view/magento/setup/updater-success.phtml @@ -1,6 +1,6 @@ diff --git a/setup/view/magento/setup/web-configuration.phtml b/setup/view/magento/setup/web-configuration.phtml index 35aa19484ed53..2fbc5f20d21d6 100644 --- a/setup/view/magento/setup/web-configuration.phtml +++ b/setup/view/magento/setup/web-configuration.phtml @@ -1,6 +1,6 @@
    - +
    @@ -40,6 +40,13 @@ $hints = [

    {{$state.current.header}}

    +
    + {{validateUrl.failed}} +
    +