diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php index aa04fa413d077e117097a5ead62a69286b5415e1..90dfb36409f5037556a420f5e50400f9a5bec7f7 100644 --- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php +++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php @@ -12,6 +12,7 @@ use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\TemporaryStateExceptionInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -137,6 +138,10 @@ class TierPriceManagement implements \Magento\Catalog\Api\ProductTierPriceManage try { $this->productRepository->save($product); } catch (\Exception $e) { + if ($e instanceof TemporaryStateExceptionInterface) { + // temporary state exception must be already localized + throw $e; + } throw new CouldNotSaveException(__('Could not save group price')); } return true; diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 6a614a65b1c5b21edfba13feafda34ec18ec8a3b..696f5e40443ca12a5786bb9ee56153ae8e350c61 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -14,6 +14,9 @@ use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; use Magento\Framework\Api\SortOrder; +use Magento\Framework\DB\Adapter\ConnectionException; +use Magento\Framework\DB\Adapter\DeadlockException; +use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -535,6 +538,24 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa unset($this->instances[$product->getSku()]); unset($this->instancesById[$product->getId()]); $this->resourceModel->save($product); + } catch (ConnectionException $exception) { + throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( + __('Database connection error'), + $exception, + $exception->getCode() + ); + } catch (DeadlockException $exception) { + throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( + __('Database deadlock found when trying to get lock'), + $exception, + $exception->getCode() + ); + } catch (LockWaitException $exception) { + throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( + __('Database lock wait timeout exceeded'), + $exception, + $exception->getCode() + ); } catch (\Magento\Eav\Model\Entity\Attribute\Exception $exception) { throw \Magento\Framework\Exception\InputException::invalidFieldValue( $exception->getAttributeCode(), diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php index 85efdbd0ce06d89feaa1d39753a6c94cc689eb40..13eb33e72d45d76067efad08b878ee99a5f4f1f8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php @@ -12,6 +12,7 @@ use \Magento\Catalog\Model\Product\TierPriceManagement; use Magento\Customer\Model\GroupManagement; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\TemporaryState\CouldNotSaveException; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -393,6 +394,30 @@ class TierPriceManagementTest extends \PHPUnit_Framework_TestCase $this->service->add('product_sku', 1, 100, 2); } + /** + * @expectedException \Magento\Framework\Exception\TemporaryState\CouldNotSaveException + */ + public function testAddRethrowsTemporaryStateExceptionIfRecoverableErrorOccurred() + { + $group = $this->getMock(\Magento\Customer\Model\Data\Group::class, [], [], '', false); + $group->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->productMock + ->expects($this->once()) + ->method('getData') + ->with('tier_price') + ->will($this->returnValue([])); + $this->groupRepositoryMock->expects($this->once()) + ->method('getById') + ->willReturn($group); + $this->repositoryMock->expects($this->once()) + ->method('save') + ->willThrowException(new CouldNotSaveException(__('Lock wait timeout'))); + + $this->service->add('product_sku', 1, 100, 2); + } + /** * @param string|int $price * @param string|float $qty diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index d9a4ff989e1990364e1460d3453b68f0e5e8fea9..13f792b78e47afd3c5457b714345a0d00ee1a051 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -12,6 +12,7 @@ namespace Magento\Catalog\Test\Unit\Model; use Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SortOrder; +use Magento\Framework\DB\Adapter\ConnectionException; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\ScopeInterface; @@ -602,6 +603,36 @@ class ProductRepositoryTest extends \PHPUnit_Framework_TestCase $this->model->save($this->productMock); } + /** + * @expectedException \Magento\Framework\Exception\TemporaryState\CouldNotSaveException + * @expectedExceptionMessage Database connection error + */ + public function testSaveThrowsTemporaryStateExceptionIfDatabaseConnectionErrorOccurred() + { + $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) + ->willThrowException(new ConnectionException('Connection lost')); + $this->extensibleDataObjectConverterMock + ->expects($this->once()) + ->method('toNestedArray') + ->will($this->returnValue($this->productData)); + $this->productMock->expects($this->once()) + ->method('getWebsiteIds') + ->willReturn([]); + + $this->model->save($this->productMock); + } + public function testDelete() { $this->productMock->expects($this->exactly(2))->method('getSku')->willReturn('product-42'); diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index aff3155ccc088432726b1e809c0930ca70a9d09f..4aa569c18cb405009e7c9f3408b945fef2fd50d7 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -209,7 +209,7 @@ <!-- post-code field has custom UI component --> <item name="component" xsi:type="string">Magento_Ui/js/form/element/post-code</item> <item name="validation" xsi:type="array"> - <item name="required-entry" xsi:type="string">true</item> + <item name="required-entry" xsi:type="boolean">true</item> </item> </item> <item name="company" xsi:type="array"> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js index 68548cb1b28d3e2a4abe66c8be13c9dd48b73ec2..be57b56efc63cf4bd696a081dd745c94e90a4190 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js @@ -19,6 +19,9 @@ define( ).fail( function (response) { errorProcessor.process(response, messageContainer); + } + ).always( + function () { fullScreenLoader.stopLoader(); } ); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js index 6f86ecedd1af3f5f85909c338ccd00e6cf390dc2..dbb38edc72413794cb761e51418e28d9afc440df 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js @@ -2,77 +2,78 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ -define( - ['ko'], - function (ko) { - 'use strict'; - var billingAddress = ko.observable(null); - var shippingAddress = ko.observable(null); - var shippingMethod = ko.observable(null); - var paymentMethod = ko.observable(null); - var quoteData = window.checkoutConfig.quoteData; - var basePriceFormat = window.checkoutConfig.basePriceFormat; - var priceFormat = window.checkoutConfig.priceFormat; - var storeCode = window.checkoutConfig.storeCode; - var totalsData = window.checkoutConfig.totalsData; - var totals = ko.observable(totalsData); - var collectedTotals = ko.observable({}); - return { - totals: totals, - shippingAddress: shippingAddress, - shippingMethod: shippingMethod, - billingAddress: billingAddress, - paymentMethod: paymentMethod, - guestEmail: null, +define([ + 'ko', + 'underscore' +], function (ko, _) { + 'use strict'; - getQuoteId: function() { - return quoteData.entity_id; - }, - isVirtual: function() { - return !!Number(quoteData.is_virtual); - }, - getPriceFormat: function() { - return priceFormat; - }, - getBasePriceFormat: function() { - return basePriceFormat; - }, - getItems: function() { - return window.checkoutConfig.quoteItemData; - }, - getTotals: function() { - return totals; - }, - setTotals: function(totalsData) { - if (_.isObject(totalsData) && _.isObject(totalsData.extension_attributes)) { - _.each(totalsData.extension_attributes, function(element, index) { - totalsData[index] = element; - }); - } - totals(totalsData); - this.setCollectedTotals('subtotal_with_discount', parseFloat(totalsData.subtotal_with_discount)); - }, - setPaymentMethod: function(paymentMethodCode) { - paymentMethod(paymentMethodCode); - }, - getPaymentMethod: function() { - return paymentMethod; - }, - getStoreCode: function() { - return storeCode; - }, - setCollectedTotals: function(code, value) { - var totals = collectedTotals(); - totals[code] = value; - collectedTotals(totals); - }, - getCalculatedTotal: function() { - var total = 0.; - _.each(collectedTotals(), function(value) { - total += value; + var billingAddress = ko.observable(null); + var shippingAddress = ko.observable(null); + var shippingMethod = ko.observable(null); + var paymentMethod = ko.observable(null); + var quoteData = window.checkoutConfig.quoteData; + var basePriceFormat = window.checkoutConfig.basePriceFormat; + var priceFormat = window.checkoutConfig.priceFormat; + var storeCode = window.checkoutConfig.storeCode; + var totalsData = window.checkoutConfig.totalsData; + var totals = ko.observable(totalsData); + var collectedTotals = ko.observable({}); + return { + totals: totals, + shippingAddress: shippingAddress, + shippingMethod: shippingMethod, + billingAddress: billingAddress, + paymentMethod: paymentMethod, + guestEmail: null, + + getQuoteId: function() { + return quoteData.entity_id; + }, + isVirtual: function() { + return !!Number(quoteData.is_virtual); + }, + getPriceFormat: function() { + return priceFormat; + }, + getBasePriceFormat: function() { + return basePriceFormat; + }, + getItems: function() { + return window.checkoutConfig.quoteItemData; + }, + getTotals: function() { + return totals; + }, + setTotals: function(totalsData) { + if (_.isObject(totalsData) && _.isObject(totalsData.extension_attributes)) { + _.each(totalsData.extension_attributes, function(element, index) { + totalsData[index] = element; }); - return total; } - }; - } -); + totals(totalsData); + this.setCollectedTotals('subtotal_with_discount', parseFloat(totalsData.subtotal_with_discount)); + }, + setPaymentMethod: function(paymentMethodCode) { + paymentMethod(paymentMethodCode); + }, + getPaymentMethod: function() { + return paymentMethod; + }, + getStoreCode: function() { + return storeCode; + }, + setCollectedTotals: function(code, value) { + var totals = collectedTotals(); + totals[code] = value; + collectedTotals(totals); + }, + getCalculatedTotal: function() { + var total = 0.; + _.each(collectedTotals(), function(value) { + total += value; + }); + return total; + } + }; +}); diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Item.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Item.php index bad43713889d7c9b8171744f69a3598c28a1021b..4a0393b745abc5106cf1e36ce5a9b8dcec54756b 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Item.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Item.php @@ -6,11 +6,12 @@ namespace Magento\Sales\Model\ResourceModel\Order; use Magento\Sales\Model\ResourceModel\EntityAbstract as SalesResource; +use Magento\Sales\Model\Spi\OrderItemResourceInterface; /** * Flat sales order item resource */ -class Item extends SalesResource +class Item extends SalesResource implements OrderItemResourceInterface { /** * Event prefix diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml index 9652061d14278327aaf4d146e3ca4c68cc51f338..775c7214c2f104771c79d5fd299229187595b167 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml @@ -90,7 +90,7 @@ $orderStoreDate = $block->formatDate( <?php if ($_order->getRemoteIp() && $block->shouldDisplayCustomerIp()): ?> <tr> <th><?php /* @escapeNotVerified */ echo __('Placed from IP') ?></th> - <td><?php /* @escapeNotVerified */ echo $_order->getRemoteIp(); echo($_order->getXForwardedFor()) ? ' (' . $block->escapeHtml($_order->getXForwardedFor()) . ')' : ''; ?></td> + <td><?php echo $block->escapeHtml($_order->getRemoteIp()); echo($_order->getXForwardedFor()) ? ' (' . $block->escapeHtml($_order->getXForwardedFor()) . ')' : ''; ?></td> </tr> <?php endif; ?> <?php if ($_order->getGlobalCurrencyCode() != $_order->getBaseCurrencyCode()): ?> diff --git a/app/code/Magento/Sales/view/frontend/email/order_new.html b/app/code/Magento/Sales/view/frontend/email/order_new.html index bb2b0bddd82672f851ce86191cf0670bc47a0eee..44a381979a89a3efc450633501ed321f13a24edc 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_new.html +++ b/app/code/Magento/Sales/view/frontend/email/order_new.html @@ -12,7 +12,7 @@ "layout handle=\"sales_email_order_items\" order=$order area=\"frontend\"":"Order Items Grid", "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", -"var order.getShippingDescription()":"Shipping Description" +"var order.getShippingDescription()":"Shipping Description", "var shipping_msg":"Shipping message" } @--> diff --git a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html index 40d55011a035efaae1883ab6781832b3fa8a6212..2a74434488a31115c666fa26197b8ca4aa089ce7 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html @@ -14,7 +14,7 @@ "layout handle=\"sales_email_order_items\" order=$order":"Order Items Grid", "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", -"var order.getShippingDescription()":"Shipping Description" +"var order.getShippingDescription()":"Shipping Description", "var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} diff --git a/app/code/Magento/Sales/view/frontend/layout/default.xml b/app/code/Magento/Sales/view/frontend/layout/default.xml index 348aa7e8af742564fa4a397f125f5ff3172bd924..ab67c031b07208edd03ab307e8471e62afc68c09 100644 --- a/app/code/Magento/Sales/view/frontend/layout/default.xml +++ b/app/code/Magento/Sales/view/frontend/layout/default.xml @@ -16,7 +16,7 @@ <referenceBlock name="footer_links"> <block class="Magento\Sales\Block\Guest\Link" name="sales-guest-form-link"> <arguments> - <argument name="label" xsi:type="string">Orders and Returns</argument> + <argument name="label" xsi:type="string" translate="true">Orders and Returns</argument> <argument name="path" xsi:type="string">sales/guest/form</argument> </arguments> </block> diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert-listing.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert-listing.js index 2ed87501b743fc63eaaa4806c87d8819310efb06..74a8a50491b2957b3af6426d0f42280a8e662f70 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/insert-listing.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert-listing.js @@ -186,12 +186,6 @@ define([ itemsType = selections && selections.excludeMode ? 'excluded' : 'selected'; rows = provider && provider.rows(); - if (_.isEmpty(selections.selected)) { - this.suppressDataLinks = false; - - return result; - } - if (this.canUpdateFromClientData(totalSelected, selections.selected, rows)) { this.updateFromClientData(selections.selected, rows); this.updateExternalValueByEditableData(); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/link.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/link.js new file mode 100644 index 0000000000000000000000000000000000000000..dbe6498f13a3819f68e38a8d9a9b3930e9a132a6 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/link.js @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + './column', + 'mageUtils' +], function (Column, utils) { + 'use strict'; + + return Column.extend({ + defaults: { + link: 'link', + bodyTmpl: 'ui/grid/cells/link' + }, + + /** + * Returns link to given record. + * + * @param {Object} record - Data to be preprocessed. + * @returns {String} + */ + getLink: function (record) { + return utils.nested(record, this.link); + }, + + /** + * Check if link parameter exist in record. + * @param {Object} record - Data to be preprocessed. + * @returns {Boolean} + */ + isLink: function (record) { + return !!utils.nested(record, this.link); + } + }); +}); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/export.js b/app/code/Magento/Ui/view/base/web/js/grid/export.js index ce7fac2bdd4774eca85848bc01ea31c5a8ca6476..ce60207b6ef43872621a36692e8709796499d2a6 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/export.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/export.js @@ -14,6 +14,7 @@ define([ template: 'ui/grid/exportButton', selectProvider: 'ns = ${ $.ns }, index = ids', checked: '', + additionalParams: [], modules: { selections: '${ $.selectProvider }' } @@ -24,6 +25,18 @@ define([ .initChecked(); }, + /** @inheritdoc */ + initConfig: function () { + this._super(); + + _.each(this.additionalParams, function (value, key) { + key = 'additionalParams.' + key; + this.imports[key] = value; + }, this); + + return this; + }, + initObservable: function () { this._super() .observe('checked'); @@ -53,6 +66,9 @@ define([ result.search = data.params.search; result.namespace = data.params.namespace; result[itemsType] = data[itemsType]; + _.each(this.additionalParams, function (param, key) { + result[key] = param; + }); if (!result[itemsType].length) { result[itemsType] = false; diff --git a/app/code/Magento/Ui/view/base/web/templates/form/components/complex.html b/app/code/Magento/Ui/view/base/web/templates/form/components/complex.html index 39f3cfe322315146f69a4e938a5b9b194ab32d61..ff7c023ff7fef12d0e6363eccaa9a5143ddf77c5 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/components/complex.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/components/complex.html @@ -16,5 +16,8 @@ render="" /> <div class="admin__field-complex-content" - html="$data.content"></div> + html="$data.content" if="$data.content"></div> + + <div class="admin__field-complex-text" + html="$data.text" if="$data.text"></div> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/text.html b/app/code/Magento/Ui/view/base/web/templates/form/element/text.html new file mode 100644 index 0000000000000000000000000000000000000000..f77b4d128b100c53052ac42c0f84fe545b10dc55 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/text.html @@ -0,0 +1,13 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<span class="admin__field-value" + data-bind=" + text: value, + attr: { + name: inputName, + id: uid + }"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/textDate.html b/app/code/Magento/Ui/view/base/web/templates/form/element/textDate.html new file mode 100644 index 0000000000000000000000000000000000000000..599f439ad6663c35dbf298591b24c274a80bc055 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/textDate.html @@ -0,0 +1,13 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<span class="admin__field-value" + data-bind=" + text: shiftedValue, + attr: { + name: inputName, + id: uid + }"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html index aeb93f48ff6832b593626c1f207d4f63d93bdc02..058d1ff5c85ddf60879a22d8e84ac239cc48518d 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html @@ -20,7 +20,7 @@ '_loading': loading, '_error': error"> <span text="label"/> - <span class="admin__page-nav-item-messages"> + <span class="admin__page-nav-item-messages" if="collapsible"> <span class="admin__page-nav-item-message _changed"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip" diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/cells/link.html b/app/code/Magento/Ui/view/base/web/templates/grid/cells/link.html new file mode 100644 index 0000000000000000000000000000000000000000..9ece01ae76ded4bfa6c63e8dccd33ef0f6875439 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/cells/link.html @@ -0,0 +1,13 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="data-grid-cell-content" + if="!$col.isLink($row())" + text="$col.getLabel($row())"/> +<a class="action-menu-item" + if="$col.isLink($row())" + text="$col.getLabel($row())" + attr="href: $col.getLink($row())"/> diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index b409ba500b9b939335ee913bf3fc8a2a32ccaebd..be4c72daaa0b8a7e85bd097635bbd88e323162b2 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -485,6 +485,7 @@ // Field containing a value .admin__field-value { + display: inline-block; padding-top: @field-option__padding-top; } @@ -740,6 +741,10 @@ overflow: hidden; } + .admin__field-complex-text { + margin-left: -@indent__s; + } + // Field with empty table and no thead + .admin__field { &._empty { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/variables/_forms.less b/app/design/adminhtml/Magento/backend/web/css/source/variables/_forms.less index 56a6f632d5631a6579cba0dc37240d12a909fdae..fd8cdc4f0945e6141776625e52778d4dea7e8c7d 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/variables/_forms.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/variables/_forms.less @@ -21,7 +21,7 @@ @field-note__font-size: @font-size__s; @field-scope__color: @color-dark-gray; -@field-option__padding-top: .8rem; +@field-option__padding-top: .7rem; @field-error-control__border-color: @color-tomato-brick; @field-error-message__background-color: @color-lazy-sun; diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html index 9a1e80aea99939fe2bf64d16a930c52026e09ceb..7dd84501b2e6032c6006cfdaf863486224e8983a 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new.html @@ -12,7 +12,7 @@ "layout handle=\"sales_email_order_items\" order=$order area=\"frontend\"":"Order Items Grid", "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", -"var order.getShippingDescription()":"Shipping Description" +"var order.getShippingDescription()":"Shipping Description", "var shipping_msg":"Shipping message" } @--> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html index 25b135c182854d8eb54c4a8fea0b7eeb36c173e2..09a1131670f1f6e857bc7c36f8d6140c31aab8f4 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html @@ -14,7 +14,7 @@ "layout handle=\"sales_email_order_items\" order=$order":"Order Items Grid", "var payment_html|raw":"Payment Details", "var formattedShippingAddress|raw":"Shipping Address", -"var order.getShippingDescription()":"Shipping Description" +"var order.getShippingDescription()":"Shipping Description", "var shipping_msg":"Shipping message" } @--> {{template config_path="design/email/header_template"}} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php index 91c746e08034f6f44d54102e3c304deb057bb9bf..8e5b687383c85f899c329cbccc7ce1624c6397ab 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php @@ -21,6 +21,18 @@ class AddressTest extends \PHPUnit_Framework_TestCase /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\AddressRepositoryInterface */ protected $addressRepository; + /** + * @return int + */ + private function getNumberOfCountryOptions() + { + /** @var \Magento\Directory\Model\ResourceModel\Country\Collection $countryCollection */ + $countryCollection = $this->_objectManager->create( + \Magento\Directory\Model\ResourceModel\Country\Collection::class + ); + return count($countryCollection->toOptionArray()); + } + protected function setUp() { $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -182,7 +194,7 @@ class AddressTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\Data\Form\Element\Select $countryIdField */ $countryIdField = $fieldset->getElements()->searchById('country_id'); - $this->assertSelectCount('option', 246, $countryIdField->getElementHtml()); + $this->assertSelectCount('option', $this->getNumberOfCountryOptions(), $countryIdField->getElementHtml()); } /** diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt index 18e66b8144dc02d8d536b01e39da92b667a9b7a1..9ae1f7fc1452d3156aa7308ee3f81d194e99bbce 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt @@ -3,3 +3,6 @@ \Magento\Framework\Config\Dom\ValidationException \Magento\Framework\Search\Request\EmptyRequestDataException \Magento\Framework\Search\Request\NonExistingRequestNameException +\Magento\Framework\DB\Adapter\ConnectionException +\Magento\Framework\DB\Adapter\DeadlockException +\Magento\Framework\DB\Adapter\LockWaitException diff --git a/lib/internal/Magento/Framework/DB/Adapter/ConnectionException.php b/lib/internal/Magento/Framework/DB/Adapter/ConnectionException.php new file mode 100644 index 0000000000000000000000000000000000000000..c465ed9726185bad7c539b80615febc44dd5d24a --- /dev/null +++ b/lib/internal/Magento/Framework/DB/Adapter/ConnectionException.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB\Adapter; + +/** + * Database connection exception + */ +class ConnectionException extends \Zend_Db_Adapter_Exception +{ +} diff --git a/lib/internal/Magento/Framework/DB/Adapter/DeadlockException.php b/lib/internal/Magento/Framework/DB/Adapter/DeadlockException.php new file mode 100644 index 0000000000000000000000000000000000000000..ccc5743a387bb5e038551fd3ca3232fccd976d96 --- /dev/null +++ b/lib/internal/Magento/Framework/DB/Adapter/DeadlockException.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB\Adapter; + +/** + * Database deadlock exception + */ +class DeadlockException extends \Zend_Db_Adapter_Exception +{ +} diff --git a/lib/internal/Magento/Framework/DB/Adapter/LockWaitException.php b/lib/internal/Magento/Framework/DB/Adapter/LockWaitException.php new file mode 100644 index 0000000000000000000000000000000000000000..240a8af8c84361bb2e569318ff728a228efee381 --- /dev/null +++ b/lib/internal/Magento/Framework/DB/Adapter/LockWaitException.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB\Adapter; + +/** + * Database lock wait exception + */ +class LockWaitException extends \Zend_Db_Adapter_Exception +{ +} diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index b8b643e9fe82f761333cdc14a9340168a08d98ac..468885548b312fba2457eb759a4a4ae4f77c3b60 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -12,6 +12,9 @@ namespace Magento\Framework\DB\Adapter\Pdo; use Magento\Framework\Cache\FrontendInterface; use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\ConnectionException; +use Magento\Framework\DB\Adapter\DeadlockException; +use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\DB\ExpressionConverter; use Magento\Framework\DB\LoggerInterface; @@ -189,6 +192,13 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface */ protected $logger; + /** + * Map that links database error code to corresponding Magento exception + * + * @var \Zend_Db_Adapter_Exception[] + */ + private $exceptionMap; + /** * @param StringUtils $string * @param DateTime $dateTime @@ -207,6 +217,16 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface $this->dateTime = $dateTime; $this->logger = $logger; $this->selectFactory = $selectFactory; + $this->exceptionMap = [ + // SQLSTATE[HY000]: General error: 2006 MySQL server has gone away + 2006 => ConnectionException::class, + // SQLSTATE[HY000]: General error: 2013 Lost connection to MySQL server during query + 2013 => ConnectionException::class, + // SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded + 1205 => LockWaitException::class, + // SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock + 1213 => DeadlockException::class, + ]; try { parent::__construct($config); } catch (\Zend_Db_Adapter_Exception $e) { @@ -487,6 +507,13 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface if (!$retry) { $this->logger->logStats(LoggerInterface::TYPE_QUERY, $sql, $bind); $this->logger->critical($e); + // rethrow custom exception if needed + if ($pdoException && isset($this->exceptionMap[$pdoException->errorInfo[1]])) { + $customExceptionClass = $this->exceptionMap[$pdoException->errorInfo[1]]; + /** @var \Zend_Db_Adapter_Exception $customException */ + $customException = new $customExceptionClass($e->getMessage(), $pdoException->errorInfo[1], $e); + throw $customException; + } throw $e; } } diff --git a/lib/internal/Magento/Framework/Exception/TemporaryState/CouldNotSaveException.php b/lib/internal/Magento/Framework/Exception/TemporaryState/CouldNotSaveException.php new file mode 100644 index 0000000000000000000000000000000000000000..3fb58936cb321c48d86b750f8e0cdc57d81fb750 --- /dev/null +++ b/lib/internal/Magento/Framework/Exception/TemporaryState/CouldNotSaveException.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Exception\TemporaryState; + +use Magento\Framework\Exception\TemporaryStateExceptionInterface; +use Magento\Framework\Exception\CouldNotSaveException as LocalizedCouldNotSaveException; +use Magento\Framework\Phrase; + +/** + * CouldNotSaveException caused by recoverable error + */ +class CouldNotSaveException extends LocalizedCouldNotSaveException implements TemporaryStateExceptionInterface +{ + /** + * Class constructor + * + * @param Phrase $phrase The Exception message to throw. + * @param \Exception $previous [optional] The previous exception used for the exception chaining. + * @param int $code [optional] The Exception code. + */ + public function __construct(Phrase $phrase, \Exception $previous = null, $code = 0) + { + parent::__construct($phrase, $previous); + $this->code = $code; + } +} diff --git a/lib/internal/Magento/Framework/Exception/TemporaryStateExceptionInterface.php b/lib/internal/Magento/Framework/Exception/TemporaryStateExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e975f8b8bbc1718538853a3338ce17d265b32bba --- /dev/null +++ b/lib/internal/Magento/Framework/Exception/TemporaryStateExceptionInterface.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Exception; + +/** + * Temporary state exception that represent recoverable error + */ +interface TemporaryStateExceptionInterface +{ +}