diff --git a/app/code/Magento/AdminAnalytics/composer.json b/app/code/Magento/AdminAnalytics/composer.json index f6c0b52be6b26..17ac5b0c4c177 100644 --- a/app/code/Magento/AdminAnalytics/composer.json +++ b/app/code/Magento/AdminAnalytics/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index 795fa8a439e78..a4b684bbb969b 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", 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 5b4d76a79bd3d..58c2f42a97efc 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/system/notification.js @@ -69,7 +69,7 @@ define([ } }); - $(document).ready(function () { + $(function () { $('#system_messages .message-system-short .error').on('click', function () { $('#message-system-all').systemMessageDialog('openModal', 1); }); diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index 8a2d73bb5b3c7..70d650a5dadf3 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json index 67f64abec55e9..9d3ec4e54db08 100644 --- a/app/code/Magento/AdvancedSearch/composer.json +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -13,7 +13,7 @@ "magento/module-customer": "*", "magento/module-search": "*", "magento/module-store": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js index ba08b8ecd291b..296b202fd2ea6 100644 --- a/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js @@ -44,7 +44,7 @@ define([ fieldToCheck = this.options.fieldToCheck || 'success'; element.removeClass('success').addClass('fail'); - $.each($.parseJSON(this.options.fieldMapping), function (key, el) { + $.each(JSON.parse(this.options.fieldMapping), function (key, el) { params[key] = $('#' + el).val(); }); $.ajax({ diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json index 2e763659c5e19..e72ee28766f14 100644 --- a/app/code/Magento/Amqp/composer.json +++ b/app/code/Magento/Amqp/composer.json @@ -8,7 +8,7 @@ "magento/framework": "*", "magento/framework-amqp": "*", "magento/framework-message-queue": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/AmqpStore/composer.json b/app/code/Magento/AmqpStore/composer.json index c47d253d51992..e3548fde0e4e8 100644 --- a/app/code/Magento/AmqpStore/composer.json +++ b/app/code/Magento/AmqpStore/composer.json @@ -8,7 +8,7 @@ "magento/framework": "*", "magento/framework-amqp": "*", "magento/module-store": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "suggest": { "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php b/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php index fecbf2033c1ba..2057c52efed17 100644 --- a/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php +++ b/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php @@ -34,6 +34,7 @@ public function __construct( * * @return array */ + #[\ReturnTypeWillChange] public function current() { $current = parent::current(); diff --git a/app/code/Magento/Analytics/ReportXml/Query.php b/app/code/Magento/Analytics/ReportXml/Query.php index b7c31d4334e20..bc56a01cbc94b 100644 --- a/app/code/Magento/Analytics/ReportXml/Query.php +++ b/app/code/Magento/Analytics/ReportXml/Query.php @@ -53,6 +53,8 @@ public function __construct( } /** + * Returns query select + * * @return Select */ public function getSelect() @@ -61,6 +63,8 @@ public function getSelect() } /** + * Returns Connection name + * * @return string */ public function getConnectionName() @@ -69,6 +73,8 @@ public function getConnectionName() } /** + * Returns configuration + * * @return array */ public function getConfig() @@ -77,11 +83,9 @@ public function getConfig() } /** - * 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. + * @inheritDoc */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json index aa891e22f3ede..de01b56de7fcc 100644 --- a/app/code/Magento/Analytics/composer.json +++ b/app/code/Magento/Analytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-backend": "*", "magento/module-config": "*", "magento/module-integration": "*", diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index 0b5a2e910a8f2..9ce426a86cf30 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -11,7 +11,7 @@ "magento/module-authorization": "*", "magento/module-backend": "*", "magento/module-ui": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "suggest": { "magento/module-admin-notification": "*", diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index 2a3b6333f5e4e..e606f9848e32f 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteFolderTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteFolderTest.xml index 6ab4fbc4e05ca..4128c74383955 100644 --- a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteFolderTest.xml +++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteFolderTest.xml @@ -89,7 +89,7 @@ - + diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteImageTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteImageTest.xml index 598065f38cd45..7f927f05bd8eb 100644 --- a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteImageTest.xml +++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminAwsS3MediaGalleryDeleteImageTest.xml @@ -82,7 +82,7 @@ - + @@ -117,7 +117,7 @@ - + diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminCreateDownloadableProductWithLinkTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminCreateDownloadableProductWithLinkTest.xml index eed46d17219e0..7232a2395f996 100644 --- a/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminCreateDownloadableProductWithLinkTest.xml +++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3AdminCreateDownloadableProductWithLinkTest.xml @@ -161,14 +161,16 @@ - + + + - - + + diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3StorefrontPrintOrderGuestTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3StorefrontPrintOrderGuestTest.xml index ce104ba19b878..b4e2a57c381bf 100644 --- a/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3StorefrontPrintOrderGuestTest.xml +++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AwsS3StorefrontPrintOrderGuestTest.xml @@ -15,9 +15,6 @@ - - - diff --git a/app/code/Magento/AwsS3/composer.json b/app/code/Magento/AwsS3/composer.json index a5e717d37636c..c81465a61bf65 100644 --- a/app/code/Magento/AwsS3/composer.json +++ b/app/code/Magento/AwsS3/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-remote-storage": "*", "league/flysystem": "^2.0", diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php index 6dae91bcc0fff..df39b5c9fcd27 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php @@ -97,7 +97,9 @@ protected function _getValue(DataObject $row) } return ''; } - return $row->getData($this->getColumn()->getIndex()); + return $this->getColumn()->getIndex() !== null + ? $row->getData($this->getColumn()->getIndex()) + : null; } /** diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php index 03566bce3fc34..19efcb7d69f61 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php @@ -102,7 +102,9 @@ protected function _getCurrencyCode($row) if ($code = $this->getColumn()->getCurrencyCode()) { return $code; } - if ($code = $row->getData($this->getColumn()->getCurrency())) { + $currency = $this->getColumn()->getCurrency(); + + if ($currency !== null && $code = $row->getData($currency)) { return $code; } @@ -118,11 +120,14 @@ protected function _getCurrencyCode($row) protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { - return (float)$rate; + return (float) $rate; } - if ($rate = $row->getData($this->getColumn()->getRateField())) { - return (float)$rate; + $rateField = $this->getColumn()->getRateField(); + + if ($rateField !== null && $rate = $row->getData($rateField)) { + return (float) $rate; } + return $this->_defaultBaseCurrency->getRate($this->_getCurrencyCode($row)); } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php index 9da23af83f036..73a10d48f3e0a 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php @@ -60,7 +60,7 @@ public function render(\Magento\Framework\DataObject $row) return $data; } - $data = (float)$data * $this->_getRate($row); + $data = (float) $data * $this->_getRate($row); $data = sprintf("%f", $data); $data = $this->_localeCurrency->getCurrency($currencyCode)->toCurrency($data); return $data; @@ -79,7 +79,9 @@ protected function _getCurrencyCode($row) if ($code = $this->getColumn()->getCurrencyCode()) { return $code; } - if ($code = $row->getData($this->getColumn()->getCurrency())) { + $currency = $this->getColumn()->getCurrency(); + + if ($currency !== null && $code = $row->getData($currency)) { return $code; } return false; @@ -94,10 +96,12 @@ protected function _getCurrencyCode($row) protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { - return (float)$rate; + return (float) $rate; } - if ($rate = $row->getData($this->getColumn()->getRateField())) { - return (float)$rate; + $rateField = $this->getColumn()->getRateField(); + + if ($rateField !== null && $rate = $row->getData($rateField)) { + return (float) $rate; } return 1; } diff --git a/app/code/Magento/Backend/Model/Menu.php b/app/code/Magento/Backend/Model/Menu.php index 0246346ec4d97..9506ac3dc36c9 100644 --- a/app/code/Magento/Backend/Model/Menu.php +++ b/app/code/Magento/Backend/Model/Menu.php @@ -242,7 +242,7 @@ public function getParentItems($itemId) * * @param \Magento\Backend\Model\Menu $menu * @param string $itemId - * @param array &$parents + * @param array $parents * @return bool */ protected function _findParentItems($menu, $itemId, &$parents) @@ -267,6 +267,7 @@ protected function _findParentItems($menu, $itemId, &$parents) * * @return string */ + #[\ReturnTypeWillChange] public function serialize() { return $this->serializer->serialize($this->toArray()); @@ -294,6 +295,7 @@ public function toArray() * @return void * @since 100.2.0 */ + #[\ReturnTypeWillChange] public function unserialize($serialized) { $data = $this->serializer->unserialize($serialized); diff --git a/app/code/Magento/Backend/Model/Menu/Filter/Iterator.php b/app/code/Magento/Backend/Model/Menu/Filter/Iterator.php index b6c61a1362edd..6bff574af988d 100644 --- a/app/code/Magento/Backend/Model/Menu/Filter/Iterator.php +++ b/app/code/Magento/Backend/Model/Menu/Filter/Iterator.php @@ -12,21 +12,12 @@ */ class Iterator extends \FilterIterator { - /** - * Constructor - * - * @param \Iterator $iterator - */ - public function __construct(\Iterator $iterator) - { - parent::__construct($iterator); - } - /** * Check whether the current element of the iterator is acceptable * * @return bool true if the current element is acceptable, otherwise false. */ + #[\ReturnTypeWillChange] public function accept() { return !($this->current()->isDisabled() || !$this->current()->isAllowed()); diff --git a/app/code/Magento/Backend/Model/Menu/Iterator.php b/app/code/Magento/Backend/Model/Menu/Iterator.php index 7e98da0571263..ac6b57a438af5 100644 --- a/app/code/Magento/Backend/Model/Menu/Iterator.php +++ b/app/code/Magento/Backend/Model/Menu/Iterator.php @@ -17,6 +17,7 @@ class Iterator extends \ArrayIterator * * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->ksort(); diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenDashboardPageActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenDashboardPageActionGroup.xml new file mode 100644 index 0000000000000..efd14d175d6a6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenDashboardPageActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + Go to Admin Dashboard page. + + + + + diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml index b96ebf2e6b559..cd6eca91e9e30 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml @@ -29,6 +29,6 @@ - + diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml index e913dca1abb33..664a241dbc1f5 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml @@ -18,5 +18,6 @@ + diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index 6af517d2bc4ba..fa05ad2b9437c 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backup": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml index c38e18b9677c6..e470913fd4c71 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml @@ -40,7 +40,7 @@ if ($isField) { data-role="escapeHtmlAttr($id) ?>-wrapper">
> escapeHtml($element->getLegend()) ?> diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index 3768e8266bb42..94754c4838f7b 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/Bundle/Model/Inventory/ChangeParentStockStatus.php b/app/code/Magento/Bundle/Model/Inventory/ChangeParentStockStatus.php new file mode 100644 index 0000000000000..023893d7317ea --- /dev/null +++ b/app/code/Magento/Bundle/Model/Inventory/ChangeParentStockStatus.php @@ -0,0 +1,160 @@ +bundleType = $bundleType; + $this->criteriaInterfaceFactory = $criteriaInterfaceFactory; + $this->stockItemRepository = $stockItemRepository; + $this->stockConfiguration = $stockConfiguration; + } + + /** + * Update stock status of bundle products based on children products stock status + * + * @param array $childrenIds + * @return void + */ + public function execute(array $childrenIds): void + { + $parentIds = $this->bundleType->getParentIdsByChild($childrenIds); + foreach (array_unique($parentIds) as $productId) { + $this->processStockForParent((int)$productId); + } + } + + /** + * Update stock status of bundle product based on children products stock status + * + * @param int $productId + * @return void + */ + private function processStockForParent(int $productId): void + { + $stockItems = $this->getStockItems([$productId]); + $parentStockItem = $stockItems[$productId] ?? null; + if ($parentStockItem) { + $childrenIsInStock = $this->isChildrenInStock($productId); + if ($this->isNeedToUpdateParent($parentStockItem, $childrenIsInStock)) { + $parentStockItem->setIsInStock($childrenIsInStock); + $parentStockItem->setStockStatusChangedAuto(1); + $this->stockItemRepository->save($parentStockItem); + } + } + } + + /** + * Returns stock status of bundle product based on children stock status + * + * Returns TRUE if any of the following conditions is true: + * - At least one product is in-stock in each required option + * - Any product is in-stock (if all options are optional) + * + * @param int $productId + * @return bool + */ + private function isChildrenInStock(int $productId) : bool + { + $childrenIsInStock = false; + $childrenIds = $this->bundleType->getChildrenIds($productId, true); + $stockItems = $this->getStockItems(array_merge(...array_values($childrenIds))); + foreach ($childrenIds as $childrenIdsPerOption) { + $childrenIsInStock = false; + foreach ($childrenIdsPerOption as $id) { + $stockItem = $stockItems[$id] ?? null; + if ($stockItem && $stockItem->getIsInStock()) { + $childrenIsInStock = true; + break; + } + } + if (!$childrenIsInStock) { + break; + } + } + + return $childrenIsInStock; + } + + /** + * Check if parent item should be updated + * + * @param StockItemInterface $parentStockItem + * @param bool $childrenIsInStock + * @return bool + */ + private function isNeedToUpdateParent( + StockItemInterface $parentStockItem, + bool $childrenIsInStock + ): bool { + return $parentStockItem->getIsInStock() !== $childrenIsInStock && + ($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto()); + } + + /** + * Get stock items for provided product IDs + * + * @param array $productIds + * @return StockItemInterface[] + */ + private function getStockItems(array $productIds): array + { + $criteria = $this->criteriaInterfaceFactory->create(); + $criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId()); + $criteria->setProductsFilter(array_unique($productIds)); + $stockItemCollection = $this->stockItemRepository->getList($criteria); + + $stockItems = []; + foreach ($stockItemCollection->getItems() as $stockItem) { + $stockItems[$stockItem->getProductId()] = $stockItem; + } + + return $stockItems; + } +} diff --git a/app/code/Magento/Bundle/Model/Inventory/ParentItemProcessor.php b/app/code/Magento/Bundle/Model/Inventory/ParentItemProcessor.php new file mode 100644 index 0000000000000..5e013ced75c33 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Inventory/ParentItemProcessor.php @@ -0,0 +1,39 @@ +changeParentStockStatus = $changeParentStockStatus; + } + + /** + * @inheritdoc + */ + public function process(Product $product) + { + $this->changeParentStockStatus->execute([$product->getId()]); + } +} diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleRegularPrice.php b/app/code/Magento/Bundle/Pricing/Price/BundleRegularPrice.php index 184f8b1e85eaa..9bda194df4b0e 100644 --- a/app/code/Magento/Bundle/Pricing/Price/BundleRegularPrice.php +++ b/app/code/Magento/Bundle/Pricing/Price/BundleRegularPrice.php @@ -7,7 +7,6 @@ namespace Magento\Bundle\Pricing\Price; use Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface; -use Magento\Catalog\Model\Product; use Magento\Framework\Pricing\Amount\AmountInterface; use Magento\Catalog\Pricing\Price\CustomOptionPrice; use Magento\Bundle\Model\Product\Price; @@ -27,36 +26,22 @@ class BundleRegularPrice extends \Magento\Catalog\Pricing\Price\RegularPrice imp */ protected $maximalPrice; - /** - * @param Product $saleableItem - * @param float $quantity - * @param BundleCalculatorInterface $calculator - * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency - */ - public function __construct( - Product $saleableItem, - $quantity, - BundleCalculatorInterface $calculator, - \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency - ) { - parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); - } - /** * @inheritdoc */ public function getAmount() { - if (!isset($this->amount[$this->getValue()])) { - $price = $this->getValue(); + $price = $this->getValue(); + $valueIndex = (string) $price; + if (!isset($this->amount[$valueIndex])) { if ($this->product->getPriceType() == Price::PRICE_TYPE_FIXED) { /** @var \Magento\Catalog\Pricing\Price\CustomOptionPrice $customOptionPrice */ $customOptionPrice = $this->priceInfo->getPrice(CustomOptionPrice::PRICE_CODE); $price += $customOptionPrice->getCustomOptionRange(true, $this->getPriceCode()); } - $this->amount[$this->getValue()] = $this->calculator->getMinRegularAmount($price, $this->product); + $this->amount[$valueIndex] = $this->calculator->getMinRegularAmount($price, $this->product); } - return $this->amount[$this->getValue()]; + return $this->amount[$valueIndex]; } /** diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup.xml new file mode 100644 index 0000000000000..50469d6690de0 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup.xml @@ -0,0 +1,28 @@ + + + + + + + Assert special price attribute value from the catalog product grid page + + + + + + + + + + + {{expectedValue}} + $getSpecialPrice + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml index 228c1d3cf1def..52cd801f42554 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml @@ -21,6 +21,14 @@ + + + + + + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml new file mode 100644 index 0000000000000..307e394913269 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml @@ -0,0 +1,86 @@ + + + + + + + + + + <description value="Admin to validate the bundle products special price column in grid should display percentage symbol instead of currency sign"/> + <severity value="AVERAGE"/> + <testCaseId value="AC-1378"/> + <useCaseId value="ACP2E-64"/> + <group value="Bundle"/> + </annotations> + <before> + <!-- Create a simple product --> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <!-- Admin login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <!-- Navigate to catalog product grid page --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/> + <!-- Open the column dropdown to add the special price from the catalog product grid --> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdownSpecialPrice"/> + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkSpecialPriceOption"> + <argument name="optionName" value="Special Price"/> + </actionGroup> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdownSpecialPrice"/> + <!-- It takes a few seconds for column update to be saved --> + <!-- waitForPageLoad won't work here since saving is happening with a short delay --> + <wait time="5" stepKey="waitForColumnUpdateToSave"/> + </before> + <after> + <!-- Navigate to catalog product grid page --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/> + <!-- Clean applied product filters before delete --> + <actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="clearAppliedFilters"/> + <!-- Delete all the products from the catalog product grid --> + <actionGroup ref="DeleteProductsIfTheyExistActionGroup" stepKey="deleteAllProducts"/> + <!-- Set product grid to default columns --> + <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="setProductGridToDefaultColumns"/> + <!-- Logging out --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + <!-- Go to bundle product creation page --> + <actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewBundleProductPage"> + <argument name="productType" value="{{BundleProduct.type}}"/> + <argument name="attributeSetId" value="{{BundleProduct.set}}"/> + </actionGroup> + <!-- Sets the provided Special Price on the Admin Product creation/edit page. --> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"> + <argument name="price" value="{{SimpleProductWithSpecialPrice.special_price}}"/> + </actionGroup> + <!-- Fill up the new product form with data --> + <actionGroup ref="CreateBasicBundleProductActionGroup" stepKey="createBundledProduct"> + <argument name="bundleProduct" value="BundleProduct"/> + </actionGroup> + <!-- Add the bundle option to the product --> + <actionGroup ref="AddBundleOptionWithOneProductActionGroup" stepKey="addBundleOption"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="{{BundleProduct.optionTitle1}}"/> + <argument name="inputType" value="{{BundleProduct.optionInputType1}}"/> + </actionGroup> + <!-- Save the bundle product --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + <!-- Navigate to catalog product grid page --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPageAfterProdSave"/> + <!-- Search the created bundle product with sku --> + <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterBundleProductGridBySku"> + <argument name="sku" value="{{BundleProduct.sku}}"/> + </actionGroup> + <!-- Asserting with the special price value contains the percentage value --> + <actionGroup ref="AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup" stepKey="assertSpecialPricePercentageSymbol"> + <argument name="expectedValue" value="90.00%"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithMultipleOptionsSuccessTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithMultipleOptionsSuccessTest.xml index 6742d276c8fed..41c8f11c84bd3 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithMultipleOptionsSuccessTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithMultipleOptionsSuccessTest.xml @@ -77,11 +77,13 @@ <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> <!-- Click place order --> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Order review page has address that was created during checkout --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Open create invoice page --> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithVirtualAndSimpleChildrenTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithVirtualAndSimpleChildrenTest.xml index 8b19753067593..548fa4d25b109 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithVirtualAndSimpleChildrenTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundlePlaceOrderWithVirtualAndSimpleChildrenTest.xml @@ -79,10 +79,12 @@ <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> <!--Click place order--> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!--Order review page has address that was created during checkout--> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!--Create Invoice for this Order--> <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="createInvoice"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest.xml index 2351a6b1b2d5f..7f80dffebae38 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest.xml @@ -112,17 +112,23 @@ <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> <!--Click place order--> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="orderNumber"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!--Navigate to order details page in custom account--> - <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="amOnOrderPage"/> + <actionGroup ref="StorefrontGoToCustomerOrderDetailsPageActionGroup" stepKey="amOnOrderPage"> + <argument name="orderId" value="$orderId"/> + <argument name="orderNumber" value="$orderNumber"/> + </actionGroup> <!--Verify bundle order items details--> <see selector="{{StorefrontCustomerOrderSection.orderItemOptionLabel('2')}}" userInput="$$createFirstBundleOption.title$$" stepKey="seeOptionLabelInCustomerOrderItems"/> <see selector="{{StorefrontCustomerOrderSection.orderItemOptionValue('3')}}" userInput="1 x $$createFirstProduct.name$$ $9.00" stepKey="seeOptionValueInCustomerOrderItems"/> <see selector="{{StorefrontCustomerOrderSection.orderItemOptionLabel('4')}}" userInput="$$createSecondBundleOption.title$$" stepKey="seeOption2LabelInCustomerOrderItems"/> <see selector="{{StorefrontCustomerOrderSection.orderItemOptionValue('5')}}" userInput="1 x $$createSecondProduct.name$$ $5.00" stepKey="seeOption2ValueInCustomerOrderItems"/> <!--Navigate to order details page on admin--> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!--Verify bundle order items details--> <see selector="{{AdminOrderItemsOrderedSection.orderItemOptionLabel('2')}}" userInput="$$createFirstBundleOption.title$$" stepKey="seeOptionLabelInAdminOrderItems"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml index 73e1e41140d8f..707e63bcb7f44 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml @@ -165,7 +165,7 @@ <scrollToTopOfPage stepKey="scrollToTop"/> <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveTaxOptions"/> - <see userInput="You saved the configuration." selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccess"/> + <waitForText userInput="You saved the configuration." selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccess"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value="cataloginventory_stock catalog_product_price"/> diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php new file mode 100644 index 0000000000000..cbe4dfa748340 --- /dev/null +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Ui\DataProvider\Product\Modifier; + +use Magento\Bundle\Model\Product\Type; +use Magento\Directory\Model\Currency as DirectoryCurrency; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; +use NumberFormatter; +use Zend_Currency; +use Zend_Currency_Exception; + +/** + * Modify product listing special price attributes + */ +class SpecialPriceAttributes implements ModifierInterface +{ + /** + * @var ResolverInterface + */ + private $localeResolver; + + /** + * @var array + */ + private $priceAttributeList; + + /** + * @var DirectoryCurrency + */ + private $directoryCurrency; + + /** + * PriceAttributes constructor. + * + * @param DirectoryCurrency $directoryCurrency + * @param ResolverInterface $localeResolver + * @param array $priceAttributeList + */ + public function __construct( + DirectoryCurrency $directoryCurrency, + ResolverInterface $localeResolver, + array $priceAttributeList = [] + ) { + $this->directoryCurrency = $directoryCurrency; + $this->localeResolver = $localeResolver; + $this->priceAttributeList = $priceAttributeList; + } + + /** + * @inheritdoc + * @throws NoSuchEntityException + * @throws Zend_Currency_Exception + */ + public function modifyData(array $data): array + { + if (empty($data) || empty($this->priceAttributeList)) { + return $data; + } + $numberFormatter = new NumberFormatter( + $this->localeResolver->getLocale(), + NumberFormatter::PERCENT + ); + $numberFormatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 2); + foreach ($data['items'] as &$item) { + foreach ($this->priceAttributeList as $priceAttribute) { + if (isset($item[$priceAttribute]) && $item['type_id'] == Type::TYPE_CODE) { + $item[$priceAttribute] = + $this->directoryCurrency->format( + $item[$priceAttribute], + ['display' => Zend_Currency::NO_SYMBOL], + false + ); + $item[$priceAttribute] = $numberFormatter->format($item[$priceAttribute] / 100); + } + } + } + return $data; + } + + /** + * @inheritdoc + */ + public function modifyMeta(array $meta): array + { + return $meta; + } +} diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index 7988bd11f88cd..8eb0125946b8e 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", @@ -21,7 +21,8 @@ "magento/module-sales": "*", "magento/module-store": "*", "magento/module-tax": "*", - "magento/module-ui": "*" + "magento/module-ui": "*", + "magento/module-directory": "*" }, "suggest": { "magento/module-webapi": "*", diff --git a/app/code/Magento/Bundle/etc/adminhtml/di.xml b/app/code/Magento/Bundle/etc/adminhtml/di.xml index b23817de3644b..c30b3482d140e 100644 --- a/app/code/Magento/Bundle/etc/adminhtml/di.xml +++ b/app/code/Magento/Bundle/etc/adminhtml/di.xml @@ -49,4 +49,21 @@ </argument> </arguments> </type> + <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Listing\Modifier\Pool"> + <arguments> + <argument name="modifiers" xsi:type="array"> + <item name="specialPriceAttributes" xsi:type="array"> + <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Modifier\SpecialPriceAttributes</item> + <item name="sortOrder" xsi:type="number">20</item> + </item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Bundle\Ui\DataProvider\Product\Modifier\SpecialPriceAttributes"> + <arguments> + <argument name="priceAttributeList" xsi:type="array"> + <item name="special_price" xsi:type="string">special_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 2ab4b2da7f2d7..c5c4a491234ed 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -72,6 +72,11 @@ </argument> </arguments> </type> + <type name="Magento\Bundle\Pricing\Price\BundleRegularPrice"> + <arguments> + <argument name="calculator" xsi:type="object">Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface</argument> + </arguments> + </type> <type name="Magento\Bundle\Pricing\Price\FinalPrice"> <arguments> <argument name="calculator" xsi:type="object">Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface</argument> @@ -278,4 +283,11 @@ </argument> </arguments> </type> + <type name="Magento\CatalogInventory\Observer\SaveInventoryDataObserver"> + <arguments> + <argument name="parentItemProcessorPool" xsi:type="array"> + <item name="bundle" xsi:type="object">Magento\Bundle\Model\Inventory\ParentItemProcessor</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml index edb6f60634032..4f0d4dc63ed93 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml @@ -13,8 +13,8 @@ <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-wrapper"> <div class="fieldset-wrapper-title"> - <strong class="admin__collapsible-title" data-toggle="collapse" - data-target="#<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-content"> + <strong class="admin__collapsible-title" data-bs-toggle="collapse" + data-bs-target="#<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-content"> <span><%- data.default_title %></span> </strong> <div class="actions"> diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php index 0f8cdc27d2417..3d479692f719a 100644 --- a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php +++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php @@ -11,8 +11,12 @@ use Magento\Bundle\Model\ResourceModel\Selection\CollectionFactory; use Magento\Bundle\Model\ResourceModel\Selection\Collection as LinkCollection; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\RuntimeException; use Magento\Framework\GraphQl\Query\EnumLookup; use Magento\Framework\GraphQl\Query\Uid; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Zend_Db_Select_Exception; /** * Collection to fetch link data at resolution time. @@ -47,20 +51,29 @@ class Collection /** @var Uid */ private $uidEncoder; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @param CollectionFactory $linkCollectionFactory * @param EnumLookup $enumLookup * @param Uid|null $uidEncoder + * @param ProductRepositoryInterface|null $productRepository */ public function __construct( CollectionFactory $linkCollectionFactory, EnumLookup $enumLookup, - Uid $uidEncoder = null + Uid $uidEncoder = null, + ?ProductRepositoryInterface $productRepository = null ) { $this->linkCollectionFactory = $linkCollectionFactory; $this->enumLookup = $enumLookup; $this->uidEncoder = $uidEncoder ?: ObjectManager::getInstance() ->get(Uid::class); + $this->productRepository = $productRepository ?: ObjectManager::getInstance() + ->get(ProductRepositoryInterface::class); } /** @@ -85,6 +98,9 @@ public function addIdFilters(int $optionId, int $parentId) : void * * @param int $optionId * @return array + * @throws NoSuchEntityException + * @throws RuntimeException + * @throws Zend_Db_Select_Exception */ public function getLinksForOptionId(int $optionId) : array { @@ -101,6 +117,10 @@ public function getLinksForOptionId(int $optionId) : array * Fetch link data and return in array format. Keys for links will be their option Ids. * * @return array + * @throws NoSuchEntityException + * @throws RuntimeException + * @throws Zend_Db_Select_Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function fetch() : array { @@ -123,26 +143,33 @@ private function fetch() : array /** @var Selection $link */ foreach ($linkCollection as $link) { + $productDetails = []; $data = $link->getData(); - $formattedLink = [ - 'price' => $link->getSelectionPriceValue(), - 'position' => $link->getPosition(), - 'id' => $link->getSelectionId(), - 'uid' => $this->uidEncoder->encode((string) $link->getSelectionId()), - 'qty' => (float)$link->getSelectionQty(), - 'quantity' => (float)$link->getSelectionQty(), - 'is_default' => (bool)$link->getIsDefault(), - 'price_type' => $this->enumLookup->getEnumValueFromField( - 'PriceTypeEnum', - (string)$link->getSelectionPriceType() - ) ?: 'DYNAMIC', - 'can_change_quantity' => $link->getSelectionCanChangeQty(), - ]; - $data = array_replace($data, $formattedLink); - if (!isset($this->links[$link->getOptionId()])) { - $this->links[$link->getOptionId()] = []; + if (isset($data['product_id'])) { + $productDetails = $this->productRepository->getById($data['product_id']); + } + + if ($productDetails && $productDetails->getIsSalable()) { + $formattedLink = [ + 'price' => $link->getSelectionPriceValue(), + 'position' => $link->getPosition(), + 'id' => $link->getSelectionId(), + 'uid' => $this->uidEncoder->encode((string)$link->getSelectionId()), + 'qty' => (float)$link->getSelectionQty(), + 'quantity' => (float)$link->getSelectionQty(), + 'is_default' => (bool)$link->getIsDefault(), + 'price_type' => $this->enumLookup->getEnumValueFromField( + 'PriceTypeEnum', + (string)$link->getSelectionPriceType() + ) ?: 'DYNAMIC', + 'can_change_quantity' => $link->getSelectionCanChangeQty(), + ]; + $data = array_replace($data, $formattedLink); + if (!isset($this->links[$link->getOptionId()])) { + $this->links[$link->getOptionId()] = []; + } + $this->links[$link->getOptionId()][] = $data; } - $this->links[$link->getOptionId()][] = $data; } return $this->links; diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json index e0e86c9735905..fa4b9430940b5 100644 --- a/app/code/Magento/BundleGraphQl/composer.json +++ b/app/code/Magento/BundleGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-catalog": "*", "magento/module-bundle": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/BundleImportExport/Plugin/Import/Product/UpdateBundleProductsStockItemStatusPlugin.php b/app/code/Magento/BundleImportExport/Plugin/Import/Product/UpdateBundleProductsStockItemStatusPlugin.php new file mode 100644 index 0000000000000..45013372a4401 --- /dev/null +++ b/app/code/Magento/BundleImportExport/Plugin/Import/Product/UpdateBundleProductsStockItemStatusPlugin.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BundleImportExport\Plugin\Import\Product; + +use Magento\CatalogImportExport\Model\StockItemImporterInterface; +use Magento\Bundle\Model\Inventory\ChangeParentStockStatus; + +/** + * Update bundle products stock item status based on children products stock status after import + */ +class UpdateBundleProductsStockItemStatusPlugin +{ + /** + * @var ChangeParentStockStatus + */ + private $changeParentStockStatus; + + /** + * @param ChangeParentStockStatus $changeParentStockStatus + */ + public function __construct( + ChangeParentStockStatus $changeParentStockStatus + ) { + $this->changeParentStockStatus = $changeParentStockStatus; + } + + /** + * Update bundle products stock item status based on children products stock status after import + * + * @param StockItemImporterInterface $subject + * @param mixed $result + * @param array $stockData + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterImport( + StockItemImporterInterface $subject, + $result, + array $stockData + ): void { + if ($stockData) { + $this->changeParentStockStatus->execute(array_column($stockData, 'product_id')); + } + } +} diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index 6cf92ae854dac..f785784526ce7 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-bundle": "*", "magento/module-store": "*", diff --git a/app/code/Magento/BundleImportExport/etc/di.xml b/app/code/Magento/BundleImportExport/etc/di.xml index 2bcbbedcc9103..d697b52f628ab 100644 --- a/app/code/Magento/BundleImportExport/etc/di.xml +++ b/app/code/Magento/BundleImportExport/etc/di.xml @@ -13,4 +13,9 @@ </argument> </arguments> </type> + <type name="Magento\CatalogImportExport\Model\StockItemImporterInterface"> + <plugin name="update_bundle_products_stock_item_status" + type="Magento\BundleImportExport\Plugin\Import\Product\UpdateBundleProductsStockItemStatusPlugin" + sortOrder="200"/> + </type> </config> diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index 028aaf4c805df..0c1c4ed6b42ea 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-page-cache": "*" }, diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaCheckoutWithEnabledCaptchaTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaCheckoutWithEnabledCaptchaTest.xml index f7f34e9833e16..3a55535e33ae0 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaCheckoutWithEnabledCaptchaTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaCheckoutWithEnabledCaptchaTest.xml @@ -59,7 +59,7 @@ <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRate"/> <actionGroup ref="StorefrontCheckoutForwardFromShippingStepActionGroup" stepKey="goToReview"/> <actionGroup ref="AssertCaptchaVisibleOnSecondCheckoutStepActionGroup" stepKey="assertCaptchaIsVisible"/> - <waitForLoadingMaskToDisappear stepKey="waitForSpinner"/> + <waitForPageLoad stepKey="waitForSpinner"/> <actionGroup ref="StorefrontFillCaptchaFieldOnCheckoutActionGroup" stepKey="placeOrderWithIncorrectValue"> <argument name="captcha" value="{{WrongCaptcha.value}}"/> </actionGroup> diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index 4da9a592251f2..9dce26daf89fd 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/CardinalCommerce/composer.json b/app/code/Magento/CardinalCommerce/composer.json index 78ea2946400ec..6d275b8e6255e 100644 --- a/app/code/Magento/CardinalCommerce/composer.json +++ b/app/code/Magento/CardinalCommerce/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-payment": "*", diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index 5dabd48782b73..28786e2429da6 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -11,7 +11,7 @@ use Magento\Framework\Exception\LocalizedException; /** - * Class Upload + * The product gallery upload controller */ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { @@ -20,7 +20,7 @@ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterf * * @see _isAllowed() */ - const ADMIN_RESOURCE = 'Magento_Catalog::products'; + public const ADMIN_RESOURCE = 'Magento_Catalog::products'; /** * @var \Magento\Framework\Controller\Result\RawFactory @@ -97,17 +97,20 @@ public function execute() $result = $uploader->save( $mediaDirectory->getAbsolutePath($this->productMediaConfig->getBaseTmpMediaPath()) ); - $this->_eventManager->dispatch( 'catalog_product_gallery_upload_image_after', ['result' => $result, 'action' => $this] ); - unset($result['tmp_name']); - unset($result['path']); + if (is_array($result)) { + unset($result['tmp_name']); + unset($result['path']); - $result['url'] = $this->productMediaConfig->getTmpMediaUrl($result['file']); - $result['file'] = $result['file'] . '.tmp'; + $result['url'] = $this->productMediaConfig->getTmpMediaUrl($result['file']); + $result['file'] = $result['file'] . '.tmp'; + } else { + $result = ['error' => 'Something went wrong while saving the file(s).']; + } } catch (LocalizedException $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } catch (\Throwable $e) { diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php index 66a9132ae44b8..dfbb80fd9d6c3 100644 --- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php +++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php @@ -10,7 +10,6 @@ use Magento\Framework\Api\SearchCriteria\CollectionProcessor\ConditionProcessor\CustomConditionInterface; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Framework\Api\Filter; -use Magento\Framework\Data\Collection\AbstractDb; use Magento\Framework\Exception\NoSuchEntityException as CategoryDoesNotExistException; /** @@ -63,12 +62,12 @@ public function build(Filter $filter): string )->where( $this->resourceConnection->getConnection()->prepareSqlCondition( 'cat.category_id', - [$this->mapConditionType($filter->getConditionType()) => $this->getCategoryIds($filter)] + ['in' => $this->getCategoryIds($filter)] ) ); $selectCondition = [ - 'in' => $categorySelect + $this->mapConditionType($filter->getConditionType()) => $categorySelect ]; return $this->resourceConnection->getConnection() @@ -116,12 +115,7 @@ private function getCategoryIds(Filter $filter): array */ private function mapConditionType(string $conditionType): string { - $conditionsMap = [ - 'eq' => 'in', - 'neq' => 'nin', - 'like' => 'in', - 'nlike' => 'nin', - ]; - return $conditionsMap[$conditionType] ?? $conditionType; + $ninConditions = ['nin', 'neq', 'nlike']; + return in_array($conditionType, $ninConditions, true) ? 'nin' : 'in'; } } diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index 0b987f05d162c..68e64a9e194d1 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -247,11 +247,11 @@ public function saveFileToTmpDir($fileId) throw new LocalizedException(__('File validation failed.')); } $result = $uploader->save($this->mediaDirectory->getAbsolutePath($baseTmpPath)); - unset($result['path']); if (!$result) { throw new LocalizedException(__('File can not be saved to the destination folder.')); } + unset($result['path']); /** * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 34ce5cad70b04..11d70583ec5a8 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -29,7 +29,7 @@ class Image extends \Magento\Framework\Model\AbstractModel /** * Config path for the jpeg image quality value */ - const XML_PATH_JPEG_QUALITY = 'system/upload_configuration/jpeg_quality'; + public const XML_PATH_JPEG_QUALITY = 'system/upload_configuration/jpeg_quality'; /** * @var int @@ -836,7 +836,37 @@ public function getWatermarkHeight() public function clearCache() { $directory = $this->_catalogProductMediaConfig->getBaseMediaPath() . '/cache'; - $this->_mediaDirectory->delete($directory); + $directoryToDelete = $directory; + // Fixes issue when deleting cache directory at the same time that images are being + // lazy-loaded on storefront leading to new directories and files generation in the cache directory + // that would prevent deletion of the cache directory. + // RCA: the method delete() recursively enumerates and delete all subdirectories and files before deleting + // the target directory, which gives other processes time to create directories and files in the same directory. + // Solution: Rename the directory to simulate deletion and delete the destination directory afterward + + try { + //generate name in format: \.[0-9A-ZA-z-_]{3} (e.g .QX3) + $tmpDirBasename = strrev(strtr(base64_encode(random_bytes(2)), '+/=', '-_.')); + $tmpDirectory = $this->_catalogProductMediaConfig->getBaseMediaPath() . '/' . $tmpDirBasename; + //delete temporary directory if exists + if ($this->_mediaDirectory->isDirectory($tmpDirectory)) { + $this->_mediaDirectory->delete($tmpDirectory); + } + //rename the directory to simulate deletion and delete the destination directory + if ($this->_mediaDirectory->isDirectory($directory) && + true === $this->_mediaDirectory->getDriver()->rename( + $this->_mediaDirectory->getAbsolutePath($directory), + $this->_mediaDirectory->getAbsolutePath($tmpDirectory) + ) + ) { + $directoryToDelete = $tmpDirectory; + } + } catch (\Throwable $exception) { + //ignore exceptions thrown during renaming + $directoryToDelete = $directory; + } + + $this->_mediaDirectory->delete($directoryToDelete); $this->_coreFileStorageDatabase->deleteFolder($this->_mediaDirectory->getAbsolutePath($directory)); $this->clearImageInfoFromCache(); @@ -870,6 +900,7 @@ protected function _fileExists($filename) public function getResizedImageInfo() { try { + $image = null; if ($this->isBaseFilePlaceholder() == true) { $image = $this->imageAsset->getSourceFile(); } else { @@ -920,6 +951,7 @@ private function getImageSize($imagePath) { $imageInfo = $this->loadImageInfoFromCache($imagePath); if (!isset($imageInfo['size'])) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $imageSize = getimagesize($imagePath); $this->saveImageInfoToCache(['size' => $imageSize], $imagePath); return $imageSize; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 246192fdc7bd5..05fd972e9fb70 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -25,6 +25,8 @@ use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; use Magento\Catalog\Model\ResourceModel\Category; +use Zend_Db_Expr; +use Magento\Catalog\Model\ResourceModel\Product\Gallery; /** * Product collection @@ -43,12 +45,12 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac /** * Alias for index table */ - const INDEX_TABLE_ALIAS = 'price_index'; + public const INDEX_TABLE_ALIAS = 'price_index'; /** * Alias for main table */ - const MAIN_TABLE_ALIAS = 'e'; + public const MAIN_TABLE_ALIAS = 'e'; /** * @var string @@ -56,57 +58,41 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected $_idFieldName = 'entity_id'; /** - * Catalog Product Flat is enabled cache per store - * * @var array */ protected $_flatEnabled = []; /** - * Product websites table name - * * @var string */ protected $_productWebsiteTable; /** - * Product categories table name - * * @var string */ protected $_productCategoryTable; /** - * Is add URL rewrites to collection flag - * * @var bool */ protected $_addUrlRewrite = false; /** - * Add URL rewrite for category - * * @var int */ protected $_urlRewriteCategory = ''; /** - * Is add final price to product collection flag - * * @var bool */ protected $_addFinalPrice = false; /** - * Cache for all ids - * * @var array */ protected $_allIdsCache = null; /** - * Is add tax percents to product collection flag - * * @var bool */ protected $_addTaxPercents = false; @@ -136,43 +122,31 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected $_priceDataFieldFilters = []; /** - * Price expression sql - * * @var string|null */ protected $_priceExpression; /** - * Additional price expression sql part - * * @var string|null */ protected $_additionalPriceExpression; /** - * Max prise (statistics data) - * * @var float */ protected $_maxPrice; /** - * Min prise (statistics data) - * * @var float */ protected $_minPrice; /** - * Prise standard deviation (statistics data) - * * @var float */ protected $_priceStandardDeviation; /** - * Prises count (statistics data) - * * @var int */ protected $_pricesCount = null; @@ -206,8 +180,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected $_scopeConfig; /** - * Customer session - * * @var \Magento\Customer\Model\Session */ protected $_customerSession; @@ -218,15 +190,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected $_localeDate; /** - * Catalog url - * * @var \Magento\Catalog\Model\ResourceModel\Url */ protected $_catalogUrl; /** - * Product option factory - * * @var \Magento\Catalog\Model\Product\OptionFactory */ protected $_productOptionFactory; @@ -249,8 +217,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected $_groupManagement; /** - * Need to add websites to result flag - * * @var bool */ protected $needToAddWebsiteNamesToResult; @@ -339,8 +305,13 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param PriceTableResolver|null $priceTableResolver * @param DimensionFactory|null $dimensionFactory * @param Category|null $categoryResourceModel + * @param DbStorage|null $urlFinder + * @param GalleryReadHandler|null $productGalleryReadHandler + * @param Gallery|null $mediaGalleryResource * * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function __construct( \Magento\Framework\Data\Collection\EntityFactory $entityFactory, @@ -368,7 +339,10 @@ public function __construct( TableMaintainer $tableMaintainer = null, PriceTableResolver $priceTableResolver = null, DimensionFactory $dimensionFactory = null, - Category $categoryResourceModel = null + Category $categoryResourceModel = null, + DbStorage $urlFinder = null, + GalleryReadHandler $productGalleryReadHandler = null, + Gallery $mediaGalleryResource = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -398,25 +372,19 @@ public function __construct( $storeManager, $connection ); - $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); - $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance() + ->get(TableMaintainer::class); + $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance() + ->get(PriceTableResolver::class); $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); $this->categoryResourceModel = $categoryResourceModel ?: ObjectManager::getInstance() ->get(Category::class); - } - - /** - * Retrieve urlFinder - * - * @return GalleryReadHandler - */ - private function getUrlFinder() - { - if ($this->urlFinder === null) { - $this->urlFinder = ObjectManager::getInstance()->get(DbStorage::class); - } - return $this->urlFinder; + $this->urlFinder = $urlFinder ?: ObjectManager::getInstance()->get(DbStorage::class); + $this->productGalleryReadHandler = $productGalleryReadHandler ?: ObjectManager::getInstance() + ->get(GalleryReadHandler::class); + $this->mediaGalleryResource = $mediaGalleryResource ?: ObjectManager::getInstance() + ->get(Gallery::class); } /** @@ -643,7 +611,7 @@ protected function _initSelect() [self::MAIN_TABLE_ALIAS => $this->getEntity()->getFlatTableName()], null )->columns( - ['status' => new \Zend_Db_Expr(ProductStatus::STATUS_ENABLED)] + ['status' => new Zend_Db_Expr(ProductStatus::STATUS_ENABLED)] ); $this->addAttributeToSelect($this->getResource()->getDefaultAttributes()); if ($this->_catalogProductFlatState->getFlatIndexerHelper()->isAddChildData()) { @@ -791,7 +759,7 @@ public function addIdFilter($productId, $exclude = false) return $this; } if (is_array($productId)) { - if (!empty($productId)) { + if ($productId) { if ($exclude) { $condition = ['nin' => $productId]; } else { @@ -1028,7 +996,7 @@ public function getMaxAttributeValue($attribute) $select->join( [$tableAlias => $attribute->getBackend()->getTable()], $condition, - [$fieldAlias => new \Zend_Db_Expr('MAX(' . $tableAlias . '.value)')] + [$fieldAlias => new Zend_Db_Expr('MAX(' . $tableAlias . '.value)')] )->group( 'e.entity_type_id' ); @@ -1065,8 +1033,8 @@ public function getAttributeValueCountByRange($attribute, $range) [$tableAlias => $attribute->getBackend()->getTable()], $condition, [ - 'count_' . $attributeCode => new \Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), - 'range_' . $attributeCode => new \Zend_Db_Expr('CEIL((' . $tableAlias . '.value+0.01)/' . $range . ')') + 'count_' . $attributeCode => new Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), + 'range_' . $attributeCode => new Zend_Db_Expr('CEIL((' . $tableAlias . '.value+0.01)/' . $range . ')') ] )->group( 'range_' . $attributeCode @@ -1104,8 +1072,8 @@ public function getAttributeValueCount($attribute) [$tableAlias => $attribute->getBackend()->getTable()], $condition, [ - 'count_' . $attributeCode => new \Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), - 'value_' . $attributeCode => new \Zend_Db_Expr($tableAlias . '.value') + 'count_' . $attributeCode => new Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), + 'value_' . $attributeCode => new Zend_Db_Expr($tableAlias . '.value') ] )->group( 'value_' . $attributeCode @@ -1311,7 +1279,7 @@ public function getProductCountSelect() 'count_table.product_id = e.entity_id', [ 'count_table.category_id', - 'product_count' => new \Zend_Db_Expr('COUNT(DISTINCT count_table.product_id)') + 'product_count' => new Zend_Db_Expr('COUNT(DISTINCT count_table.product_id)') ] )->where( 'count_table.store_id = ?', @@ -1490,7 +1458,7 @@ protected function _addUrlRewrite() $filter['metadata']['category_id'] = $this->_urlRewriteCategory; } - $rewrites = $this->getUrlFinder()->findAllByData($filter); + $rewrites = $this->urlFinder->findAllByData($filter); foreach ($rewrites as $rewrite) { if ($item = $this->getItemById($rewrite->getEntityId())) { $item->setData('request_path', $rewrite->getRequestPath()); @@ -2090,28 +2058,25 @@ protected function _applyZeroStoreProductLimitations() { $filters = $this->_productLimitationFilters; $categories = $this->getChildrenCategories((int)$filters['category_id']); + $joinCond = 'cat_pro.product_id = e.entity_id'; - $conditions = [ - 'cat_pro.product_id=e.entity_id', - $this->getConnection()->quoteInto( - 'cat_pro.category_id IN (?)', - $categories - ), - ]; - $joinCond = join(' AND ', $conditions); - - $fromPart = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM); + $fromPart = $this->getSelect()->getPart(Select::FROM); if (isset($fromPart['cat_pro'])) { $fromPart['cat_pro']['joinCondition'] = $joinCond; - $this->getSelect()->setPart(\Magento\Framework\DB\Select::FROM, $fromPart); + $this->getSelect()->setPart(Select::FROM, $fromPart); } else { + $conditions = [ + $joinCond, + $this->getConnection()->quoteInto('cat_pro.category_id IN(?)', $categories, 'int'), + ]; + $joinCond = join(' AND ', $conditions); $this->getSelect()->join( ['cat_pro' => $this->getTable('catalog_category_product')], $joinCond, - ['cat_index_position' => 'position'] - ); + ['cat_index_position' => new Zend_Db_Expr('MIN(cat_pro.position)')] + )->group('e.entity_id'); } - $this->_joinFields['position'] = ['table' => 'cat_pro', 'field' => 'position']; + $this->_joinFields['position'] = ['table' => 'cat_pro', 'field' => 'min_position']; return $this; } @@ -2370,7 +2335,7 @@ public function addMediaGalleryData() $items = $this->getItems(); $linkField = $this->getProductEntityMetadata()->getLinkField(); - $select = $this->getMediaGalleryResource() + $select = $this->mediaGalleryResource ->createBatchBaseSelect( $this->getStoreId(), $this->getAttribute('media_gallery')->getAttributeId() @@ -2392,7 +2357,7 @@ function ($item) use ($linkField) { } foreach ($items as $item) { - $this->getGalleryReadHandler() + $this->productGalleryReadHandler ->addMediaDataToProduct( $item, $mediaGalleries[$item->getOrigData($linkField)] ?? [] @@ -2414,35 +2379,6 @@ public function getProductEntityMetadata() return $this->metadataPool->getMetadata(ProductInterface::class); } - /** - * Retrieve GalleryReadHandler - * - * @return GalleryReadHandler - * @deprecated 101.0.1 - */ - private function getGalleryReadHandler() - { - if ($this->productGalleryReadHandler === null) { - $this->productGalleryReadHandler = ObjectManager::getInstance()->get(GalleryReadHandler::class); - } - return $this->productGalleryReadHandler; - } - - /** - * Retrieve Media gallery resource. - * - * @deprecated 101.0.1 - * - * @return \Magento\Catalog\Model\ResourceModel\Product\Gallery - */ - private function getMediaGalleryResource() - { - if (null === $this->mediaGalleryResource) { - $this->mediaGalleryResource = ObjectManager::getInstance()->get(Gallery::class); - } - return $this->mediaGalleryResource; - } - /** * Clear collection * @@ -2551,7 +2487,7 @@ private function addIsSaleableAttributeToFilter($condition): self foreach ($columns as $columnEntry) { list($correlationName, $column, $alias) = $columnEntry; if ($alias == 'is_saleable') { - if ($column instanceof \Zend_Db_Expr) { + if ($column instanceof Zend_Db_Expr) { $field = $column; } else { $connection = $this->getSelect()->getConnection(); 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 3a42d8ad7dd0b..91dfd21ba3cf7 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection/ProductLimitation.php @@ -6,8 +6,6 @@ namespace Magento\Catalog\Model\ResourceModel\Product\Collection; /** - * Class ProductLimitation - * * @api * @since 101.0.0 */ @@ -31,47 +29,61 @@ class ProductLimitation implements \ArrayAccess private $productLimitationFilters = []; /** + * Check if the value is set for the given offset. + * * @param string $offset * @return bool * @since 101.0.0 */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->productLimitationFilters); } /** + * Get the value by provided offset. + * * @param string $offset * @return mixed * @since 101.0.0 */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->productLimitationFilters[$offset]; } /** + * Set the given offset to filters. + * * @param string $offset * @param mixed $value * @return void * @since 101.0.0 */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->productLimitationFilters[$offset] = $value; } /** + * Unset the given offset from filters. + * * @param string $offset * @return void * @since 101.0.0 */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->productLimitationFilters[$offset]); } /** + * Returns store ID. + * * @return int|null * @since 101.0.0 */ @@ -81,6 +93,8 @@ public function getStoreId() } /** + * Returns category ID. + * * @return int|null * @since 101.0.0 */ @@ -90,6 +104,8 @@ public function getCategoryId() } /** + * Check if the category is an anchor. + * * @return int|null * @since 101.0.0 */ @@ -99,6 +115,8 @@ public function getCategoryIsAnchor() } /** + * Returns visibility value. + * * @return array|int|null * @since 101.0.0 */ @@ -108,6 +126,8 @@ public function getVisibility() } /** + * Returns website IDs. + * * @return array|int|null * @since 101.0.0 */ @@ -117,6 +137,8 @@ public function getWebsiteIds() } /** + * Returns Store table. + * * @return string|null * @since 101.0.0 */ @@ -137,13 +159,15 @@ public function isUsingPriceIndex() } /** + * Set 'use price index' offset. + * * @param bool $value * @return void * @since 101.0.0 */ public function setUsePriceIndex($value) { - $this->offsetSet('use_price_index', (bool)$value); + $this->offsetSet('use_price_index', (bool) $value); } /** diff --git a/app/code/Magento/Catalog/Model/Theme/CustomerData/MessagesProvider.php b/app/code/Magento/Catalog/Model/Theme/CustomerData/MessagesProvider.php new file mode 100644 index 0000000000000..b332a8134527f --- /dev/null +++ b/app/code/Magento/Catalog/Model/Theme/CustomerData/MessagesProvider.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Theme\CustomerData; + +use Magento\Catalog\Model\Product\ProductFrontendAction\Synchronizer; +use Magento\Framework\App\Config; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Message\Collection; +use Magento\Framework\Message\ManagerInterface as MessageManager; +use Magento\Theme\CustomerData\MessagesProviderInterface; + +class MessagesProvider implements MessagesProviderInterface +{ + /** + * + * @var Config + */ + private $appConfig; + + /** + * @var RequestInterface + */ + private $request; + + /** + * Manager messages + * + * @var MessageManager + */ + private $messageManager; + + /** + * Constructor + * + * @param Config $appConfig + * @param RequestInterface $request + * @param MessageManager $messageManager + */ + public function __construct( + Config $appConfig, + RequestInterface $request, + MessageManager $messageManager + ) { + $this->appConfig = $appConfig; + $this->request = $request; + $this->messageManager = $messageManager; + } + + /** + * Verify flag value for synchronize product actions with backend or not + * + * @return Collection + */ + public function getMessages(): Collection + { + $clearSessionMessages = true; + + if ((bool) $this->appConfig->getValue(Synchronizer::ALLOW_SYNC_WITH_BACKEND_PATH)) { + $clearSessionMessages = $this->request->getParam('force_new_section_timestamp') === 'true'; + } + + return $this->messageManager->getMessages($clearSessionMessages); + } +} diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php index fc83d7b9716a8..846784718d023 100644 --- a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php +++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php @@ -59,9 +59,10 @@ public function getAliases() public function apply() { $this->dataSetup->startSetup(); + $setup = $this->dataSetup; + $connection = $setup->getConnection(); - $connection = $this->dataSetup->getConnection(); - $attributeTable = $connection->getTableName('eav_attribute'); + $attributeTable = $setup->getTable('eav_attribute'); /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $this->dataSetup]); $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY); @@ -74,8 +75,8 @@ public function apply() ->where('frontend_input = ?', 'multiselect') ); - $varcharTable = $connection->getTableName('catalog_product_entity_varchar'); - $textTable = $connection->getTableName('catalog_product_entity_text'); + $varcharTable = $setup->getTable('catalog_product_entity_varchar'); + $textTable = $setup->getTable('catalog_product_entity_text'); $varcharTableDataSql = $connection ->select() ->from($varcharTable) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddRelatedProductBySkuActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddRelatedProductBySkuActionGroup.xml index ce62acf6dd32a..d8730ca831fd1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddRelatedProductBySkuActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddRelatedProductBySkuActionGroup.xml @@ -16,11 +16,17 @@ <argument name="sku"/> </arguments> - <!--Scroll up to avoid error--> - <scrollTo selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" x="0" y="-100" stepKey="scrollTo"/> + <!--Scroll to element to avoid test order flakiness--> + <waitForElement selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" stepKey="waitForSection"/> + <executeJS function="return document.evaluate("{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight"/> + <executeJS function="window.scrollTo({top: {$sectionPosition}-{$floatingHeaderHeight}})" stepKey="scrollTo"/> <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedUpSellCrossSell"/> + <waitForPageLoad stepKey="waitForSectionExpanded"/> <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.AddRelatedProductsButton}}" stepKey="clickAddRelatedProductButton"/> + <waitForPageLoad stepKey="waitForRelatedProductsGrid"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForFiltersCleared"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductAttributeOnProductEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductAttributeOnProductEditPageActionGroup.xml index faf9d4f40648d..4ca50eaf556d3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductAttributeOnProductEditPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductAttributeOnProductEditPageActionGroup.xml @@ -16,8 +16,13 @@ <argument name="attributeCode" defaultValue="{{newProductAttribute.attribute_code}}" type="string"/> <argument name="attributeLabel" defaultValue="{{ProductAttributeFrontendLabel.label}}" type="string"/> </arguments> + <!--Scroll to element to avoid test order flakiness--> + <waitForElement selector="{{AdminProductFormSection.attributeTab}}" stepKey="waitForSection"/> + <executeJS function="return document.evaluate("{{AdminProductFormSection.attributeTab}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight"/> + <executeJS function="window.scrollTo({top: {$sectionPosition}-{$floatingHeaderHeight}})" stepKey="scrollToAttributesTab"/> <conditionalClick selector="{{AdminProductFormSection.attributeTab}}" dependentSelector="{{AdminProductFormSection.attributeTabOpened}}" visible="false" stepKey="clickToOpen"/> - <scrollTo selector="{{AdminProductFormSection.attributeTab}}" stepKey="scrollToAttributeTab"/> + <comment userInput="BIC workaround" stepKey="scrollToAttributeTab"/> <seeElement selector="{{AdminProductFormSection.attributeLabelByText(attributeLabel)}}" stepKey="seeAttributeLabelInProductForm"/> <seeElement selector="{{AdminProductFormSection.newAddedAttribute(attributeCode)}}" stepKey="seeProductAttributeIsAdded"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml index a423681c0a490..1f91076c49339 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml @@ -13,7 +13,11 @@ <description>Expands the 'Attributes' tab on the Admin Product page.</description> </annotations> - <scrollTo selector="{{AdminProductAttributeSection.attributeSectionHeader}}" stepKey="scrollToAttributesTab"/> + <!--Scroll to element to avoid test order flakiness--> + <waitForElement selector="{{AdminProductAttributeSection.attributeSectionHeader}}" stepKey="waitForSection"/> + <executeJS function="return document.evaluate("{{AdminProductAttributeSection.attributeSectionHeader}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight"/> + <executeJS function="window.scrollTo({top: {$sectionPosition}-{$floatingHeaderHeight}})" stepKey="scrollToAttributesTab"/> <conditionalClick selector="{{AdminProductAttributeSection.attributeSectionHeader}}" dependentSelector="{{AdminProductAttributeSection.attributeSection}}" visible="false" stepKey="expandAttributesTab"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductFormSaveActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductFormSaveActionGroup.xml index 3db88bf3054bf..cb77e1d057544 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductFormSaveActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductFormSaveActionGroup.xml @@ -13,6 +13,8 @@ <description>Click save button for saving product.</description> </annotations> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <waitForElementVisible selector="{{AdminProductFormActionSection.saveButton}}" stepKey="waitForSaveButton"/> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> <waitForPageLoad stepKey="waitForProductSaving"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CategoryPresentActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CategoryPresentActionGroup.xml index 76aec793c4eca..72891a655a097 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CategoryPresentActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CategoryPresentActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="CategoryPresentActionGroup" > + <actionGroup name="CategoryPresentActionGroup" deprecated="Do not use action groups that combine multiple workflows. Instead, use AdminOpenCategoryPageActionGroup to navigate to the admin category page, then assert that the category name is present, then use StorefrontNavigateToCategoryUrlActionGroup to navigate to the storefront category page, then assert that the category name is present."> <annotations> <description>Navigates to category page, asserts category is there. Navigates to storefront category page and asserts category is there. This action group will not work categories where name does NOT equal SEO.</description> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddCategoryProductToCompareActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddCategoryProductToCompareActionGroup.xml index d4678a97c5bc2..1ca7ba4d96d7f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddCategoryProductToCompareActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddCategoryProductToCompareActionGroup.xml @@ -18,8 +18,10 @@ </arguments> <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(productVar.name)}}" stepKey="moveMouseOverProduct"/> + <waitForElementVisible selector="{{StorefrontCategoryProductSection.ProductAddToCompareByName(productVar.name)}}" stepKey="waitForAddProductToCompare"/> <click selector="{{StorefrontCategoryProductSection.ProductAddToCompareByName(productVar.name)}}" stepKey="clickAddProductToCompare"/> + <waitForPageLoad stepKey="waitForPageLoad"/> <waitForElement selector="{{StorefrontMessagesSection.success}}" time="30" stepKey="waitForAddCategoryProductToCompareSuccessMessage"/> - <see selector="{{StorefrontMessagesSection.success}}" userInput="You added product {{productVar.name}} to the comparison list." stepKey="assertAddCategoryProductToCompareSuccessMessage"/> + <waitForText selector="{{StorefrontMessagesSection.success}}" userInput="You added product {{productVar.name}} to the comparison list." stepKey="assertAddCategoryProductToCompareSuccessMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml index 24e1fe9cf5ecd..ae083c0dee132 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml @@ -14,7 +14,7 @@ <data key="dataUiId">magento-catalog-catalog</data> </entity> <entity name="AdminMenuCatalogCategories"> - <data key="pageTitle">Default Category (ID: 2)</data> + <data key="pageTitle">Default Category (ID: </data> <data key="title">Categories</data> <data key="dataUiId">magento-catalog-catalog-categories</data> </entity> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogConfigurationData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogConfigurationData.xml index cb2bacfd2f2da..274e44473967d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogConfigurationData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogConfigurationData.xml @@ -22,4 +22,18 @@ <data key="label">Website</data> <data key="value">1</data> </entity> + <!-- Catalog > Recently Viewed/Compared Products > Synchronize Widget Products With Backend Storage --> + <entity name="DisableSynchronizeWidgetProductsWithBackendStorage"> + <!-- Default configuration --> + <data key="path">catalog/recently_products/synchronize_with_backend</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="EnableSynchronizeWidgetProductsWithBackendStorage"> + <data key="path">catalog/recently_products/synchronize_with_backend</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml index 14bf132c6a0cb..56eead213425e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryMessagesSection"> - <element name="SuccessMessage" type="text" selector=".message-success"/> + <element name="SuccessMessage" type="text" selector=".page-content .message-success"/> <element name="errorMessage" type="text" selector="//div[@class='message message-error error']"/> <element name="saveCategoryWarningMessage" type="text" selector=".message-warning"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index c94bca1ca5c13..4a54997a145ee 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -22,5 +22,6 @@ <element name="expandRootCategory" type="text" selector="img.x-tree-elbow-end-plus"/> <element name="expandRootCategoryByName" type="button" selector="//div[@class='x-tree-root-node']/li/div/a/span[contains(., '{{categoryName}}')]/../../img[contains(@class, 'x-tree-elbow-end-plus')]" parameterized="true" timeout="30"/> <element name="categoryByName" type="text" selector="//div[contains(@class, 'categories-side-col')]//a/span[contains(text(), '{{categoryName}}')]" parameterized="true" timeout="30"/> + <element name="expandCategoryByName" type="text" selector="//span[contains(text(),'{{categoryName}}')]/ancestor::div[contains(@class,'x-tree-node-el')]//img[contains(@class,'x-tree-elbow-end-plus') or contains(@class,'x-tree-elbow-plus')]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml index dba1ea040c825..d36e8740c1343 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml @@ -25,30 +25,40 @@ </before> <after> - <!-- Delete the created category --> - <actionGroup ref="DeleteMostRecentCategoryActionGroup" stepKey="getRidOfCreatedCategory"/> - <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> + <!-- Delete the created data --> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminDeleteCategoryByNameActionGroup" stepKey="deleteCategory"> + <argument name="categoryName" value="{{_defaultCategory.name}}"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="getRidOfCreatedCategory"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> - <!-- Find the product that we just created using the product grid and go to its page--> - <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="visitAdminProductPage"/> - <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="findCreatedProduct"> - <argument name="product" value="SimpleTwo"/> + <!-- Go to created product in admin --> + <comment userInput="BIC workaround" stepKey="visitAdminProductPage"/> + <comment userInput="BIC workaround" stepKey="clickClearFiltersInitial"/> + <comment userInput="BIC workaround" stepKey="findCreatedProduct"/> + <comment userInput="BIC workaround" stepKey="waitForFiltersToBeApplied"/> + <comment userInput="BIC workaround" stepKey="clickOnProductPage"/> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="navigateToProductPage"> + <argument name="productId" value="$simpleProduct.id$"/> </actionGroup> - <waitForPageLoad stepKey="waitForFiltersToBeApplied"/> - <actionGroup ref="AdminProductGridSectionClickFirstRowActionGroup" stepKey="clickOnProductPage"/> <!-- Fill out the form for the new category --> <actionGroup ref="FillNewProductCategoryActionGroup" stepKey="FillNewProductCategory"> <argument name="categoryName" value="{{_defaultCategory.name}}"/> </actionGroup> - <!-- Check that category was created --> - <actionGroup ref="CategoryPresentActionGroup" stepKey="checkIfCategoryPresent"> - <argument name="categoryName" value="{{_defaultCategory.name}}"/> - </actionGroup> + <!-- Check in admin that category was created --> + <comment userInput="BIC workaround" stepKey="checkIfCategoryPresent"/> + <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="goToCategoryAdminPage"/> + <actionGroup ref="AdminCategoriesExpandAllActionGroup" stepKey="expandAllCategories"/> + <see userInput="{{_defaultCategory.name}}" selector="{{AdminCategorySidebarTreeSection.treeContainer}}" stepKey="assertCategoryOnAdminPage"/> + <!-- Check on storefront that category was created --> + <actionGroup ref="StorefrontNavigateToCategoryUrlActionGroup" stepKey="goToCategoryStorefrontPage"> + <argument name="categoryUrl" value="{{_defaultCategory.urlKey}}"/> + </actionGroup> + <see userInput="{{_defaultCategory.name}}" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="assertCategoryNameOnStorefront"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml index b7a55a90a08d3..51ae693ce1272 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml @@ -38,6 +38,7 @@ <!-- Search and select products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductAttributeUpdateAddedToQueueTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductAttributeUpdateAddedToQueueTest.xml index dc34607f2a771..c887ed5b42816 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductAttributeUpdateAddedToQueueTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductAttributeUpdateAddedToQueueTest.xml @@ -34,6 +34,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml index 8a5da6d6e3640..00c466e9aebec 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml @@ -31,7 +31,8 @@ <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> - <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="searchByKeyword"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> + <comment userInput="BIC workaround" stepKey="searchByKeyword"/> <actionGroup ref="SortProductsByIdDescendingActionGroup" stepKey="sortProductsByIdDescending"/> @@ -43,46 +44,46 @@ </actionGroup> <actionGroup ref="AdminClickMassUpdateProductAttributesActionGroup" stepKey="clickDropdown"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="clickChangeStatus"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForProductAttributePageToLoad"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="clickChangeStatus"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForProductAttributePageToLoad"/> - <actionGroup ref="AdminSetPriceForMassUpdateActionGroup" stepKey="scrollToPriceCheckBox"> + <actionGroup ref="AdminSetPriceForMassUpdateActionGroup" stepKey="scrollToPriceCheckBox"> <argument name="price" value="90.99"/> - </actionGroup> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="selectPriceCheckBox"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="fillPrice"/> + </actionGroup> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="selectPriceCheckBox"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="fillPrice"/> <actionGroup ref="AdminSaveProductsMassAttributesUpdateActionGroup" stepKey="clickOnSaveButton"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForUpdatedProductToSave"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeAttributeUpateSuccessMsg"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForUpdatedProductToSave"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeAttributeUpateSuccessMsg"/> <actionGroup ref="CliConsumerStartActionGroup" stepKey="startMessageQueueConsumer"> <argument name="consumerName" value="{{AdminProductAttributeUpdateConsumerData.consumerName}}"/> <argument name="maxMessages" value="{{AdminProductAttributeUpdateConsumerData.messageLimit}}"/> </actionGroup> - + <magentoCLI command="cron:run --group=index" stepKey="runCron"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="openFirstProduct"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="openFirstProduct"/> <actionGroup ref="AssertAdminProductPriceUpdatedOnEditPageActionGroup" stepKey="waitForFirstProductToLoad"> <argument name="product" value="$$simpleProduct1$$"/> <argument name="price" value="90.99"/> </actionGroup> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductNameInField"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductSkuInField"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductPriceInField"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="clickOnBackButton"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductNameInField"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductSkuInField"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeFirstProductPriceInField"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="clickOnBackButton"/> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="waitForProductsToLoad"/> - - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="openSecondProduct"/> + + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="openSecondProduct"/> <actionGroup ref="AssertAdminProductPriceUpdatedOnEditPageActionGroup" stepKey="waitForSecondProductToLoad"> <argument name="product" value="$$simpleProduct2$$"/> <argument name="price" value="90.99"/> - </actionGroup> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductNameInField"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductSkuInField"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductPriceInField"/> - + </actionGroup> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductNameInField"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductSkuInField"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSecondProductPriceInField"/> + </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributeDatetimeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributeDatetimeTest.xml index 161c349b4d29e..807950ff25c23 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributeDatetimeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributeDatetimeTest.xml @@ -47,6 +47,7 @@ <generateDate date="+1 day" format="m/j/Y g:i A" stepKey="randomDatetime"/> <!-- Navigate to products list page and select created products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml index 35a3a39422185..926ae823195db 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -45,6 +45,7 @@ <!-- Search and select products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml index ef74e71a4c502..c8aba75838f52 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml @@ -37,6 +37,7 @@ </after> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminDataGridSelectPerPageActionGroup" stepKey="selectNumberOfProductsPerPage"> <argument name="perPage" value="100"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml index 44df83a4a7299..cef244bc049a7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml @@ -36,6 +36,7 @@ <!-- Search and select products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductQtyIncrementsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductQtyIncrementsTest.xml index aa82b092a0a98..c54fb0b947e40 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductQtyIncrementsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductQtyIncrementsTest.xml @@ -40,6 +40,7 @@ </after> <!-- Navigate to products list page and select created products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="api-simple-product"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml index b1dad54edcae9..13171be4103e7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml @@ -67,6 +67,7 @@ <!-- Search and select products --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="SearchProductGridByKeyword2ActionGroup" stepKey="searchByKeyword"> <argument name="keyword" value="{{simpleProductForMassUpdate.keyword}}"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductRemoveImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductRemoveImagesTest.xml index 9dae1259cc4f9..eff423989cd0e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductRemoveImagesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductRemoveImagesTest.xml @@ -49,8 +49,11 @@ <actionGroup ref="AdminOpenProductImagesSectionActionGroup" stepKey="expandImages"/> <!-- Upload and set Base image --> - <attachFile selector="{{AdminProductImagesSection.imageFileUpload}}" userInput="adobe-base.jpg" stepKey="attach1"/> - <waitForPageLoad stepKey="waitForUpload1"/> + <actionGroup ref="AddProductImageActionGroup" stepKey="attach1"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForUpload1"/> + <waitForElementVisible selector="{{AdminProductImagesSection.nthProductImage('1')}}" stepKey="waitForOpenImageDetails1"/> <click selector="{{AdminProductImagesSection.nthProductImage('1')}}" stepKey="openImageDetails1"/> <waitForPageLoad stepKey="waitForSlideout1"/> <conditionalClick selector="{{AdminProductImagesSection.roleBase}}" dependentSelector="{{AdminProductImagesSection.isBaseSelected}}" visible="false" stepKey="base1"/> @@ -61,8 +64,11 @@ <waitForPageLoad stepKey="waitForHide1"/> <!-- Upload and set Small image --> - <attachFile selector="{{AdminProductImagesSection.imageFileUpload}}" userInput="adobe-small.jpg" stepKey="attach2"/> - <waitForPageLoad stepKey="waitForUpload2"/> + <actionGroup ref="AddProductImageActionGroup" stepKey="attach2"> + <argument name="image" value="AdobeSmallImage"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForUpload2"/> + <waitForElementVisible selector="{{AdminProductImagesSection.nthProductImage('2')}}" stepKey="waitForOpenImageDetails2"/> <click selector="{{AdminProductImagesSection.nthProductImage('2')}}" stepKey="openImageDetails2"/> <waitForPageLoad stepKey="waitForSlideout2"/> <conditionalClick selector="{{AdminProductImagesSection.roleBase}}" dependentSelector="{{AdminProductImagesSection.isBaseSelected}}" visible="true" stepKey="base2"/> @@ -73,8 +79,11 @@ <waitForPageLoad stepKey="waitForHide2"/> <!-- Upload and set Thumbnail image --> - <attachFile selector="{{AdminProductImagesSection.imageFileUpload}}" userInput="adobe-thumb.jpg" stepKey="attach3"/> - <waitForPageLoad stepKey="waitForUpload3"/> + <actionGroup ref="AddProductImageActionGroup" stepKey="attach3"> + <argument name="image" value="AdobeThumbImage"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForUpload3"/> + <waitForElementVisible selector="{{AdminProductImagesSection.nthProductImage('3')}}" stepKey="waitForOpenImageDetails3"/> <click selector="{{AdminProductImagesSection.nthProductImage('3')}}" stepKey="openImageDetails3"/> <waitForPageLoad stepKey="waitForSlideout3"/> <conditionalClick selector="{{AdminProductImagesSection.roleBase}}" dependentSelector="{{AdminProductImagesSection.isBaseSelected}}" visible="true" stepKey="base3"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml index 41c187975a92b..c927988b39494 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml @@ -26,6 +26,9 @@ <requiredEntity createDataKey="initialCategoryEntity"/> </createData> <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> </before> <after> <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteDefaultVirtualProduct"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml index ac855cdbf94af..6b1355b01e473 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml @@ -34,6 +34,9 @@ <requiredEntity createDataKey="createCategoryB"/> </createData> <createData entity="NewRootCategory" stepKey="createNewRootCategoryA"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminSystemStoreOpenPageActionGroup" stepKey="navigateToStores"/> + <actionGroup ref="AdminDeleteMultipleWebsitesActionGroup" stepKey="deleteWebsites"/> </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> @@ -41,7 +44,7 @@ <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> <deleteData createDataKey="createProduct3" stepKey="deleteProduct3"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> + <comment userInput="BIC workaround" stepKey="loginAsAdmin1"/> <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="navigateToCategoryPage1"/> <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createNewRootCategoryA.name$$)}}" stepKey="openNewRootCategory"/> <waitForPageLoad stepKey="waitForPageCategoryLoadAfterClickOnNewRootCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index 7968141a6fb0c..819d7b075fc3a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -78,7 +78,7 @@ <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle('Radio Option', '2')}}" userInput="option 3" stepKey="fillOptionValueTitle3"/> <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice('Radio Option', '2')}}" userInput="7" stepKey="fillOptionValuePrice3"/> - <!--Save the product with custom options --> + <!-- Save the product with custom options --> <actionGroup ref="AdminProductFormSaveActionGroup" stepKey="clickSaveButton"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeProductSavedMessage"/> @@ -89,7 +89,11 @@ <waitForLoadingMaskToDisappear stepKey="waitForProductPagetoSaveAgain"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessageAgain"/> - <scrollTo selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="scrollToOptions"/> + <!-- Verify the product's custom options --> + <waitForElement selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="waitForSection"/> + <executeJS function="return document.evaluate("{{AdminProductCustomizableOptionsSection.customizableOptions}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight"/> + <executeJS function="window.scrollTo({top: {$sectionPosition}-{$floatingHeaderHeight}})" stepKey="scrollToOptions"/> <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomOptionsSection2"/> <waitForElementVisible selector=".admin__dynamic-rows[data-index='values'] tr.data-row" stepKey="waitForRowsToBeVisible"/> <seeNumberOfElements selector=".admin__dynamic-rows[data-index='values'] tr.data-row" userInput="3" stepKey="see4RowsOfOptions"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml index 38f72fe24bb46..e7808728acbae 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml @@ -30,9 +30,20 @@ </before> <after> <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setCurrencyAllow"/> - <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setAllowedCurrencyUSDWebsites"/> + <comment userInput="BIC workaround" stepKey="setAllowedCurrencyUSDWebsites"/> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Revert Allowed Currency to System Default --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminNavigateToCurrencySetupPageActionGroup" stepKey="goToCurrencySetupPage"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchToMainWebsite"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValueForAllowedCurrency"> + <argument name="rowId" value="row_currency_options_allow"/> + </actionGroup> + <actionGroup ref="SaveStoreConfigurationActionGroup" stepKey="saveStoreConfiguration"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPageOnStorefront"> <argument name="product" value="$createProduct$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml index 020c3d85b9fdf..f4b535cb7af66 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml @@ -63,6 +63,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <deleteData createDataKey="createFirstAttribute" stepKey="deleteFirstAttribute"/> <deleteData createDataKey="createSecondAttribute" stepKey="deleteSecondAttribute"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index dad3e05d81a53..92985adbc8cf8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -122,17 +122,20 @@ <!-- Place Order --> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForPlaceOrderButton"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="orderNumber"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Login to Admin and open Order --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> - - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterByOrderId"> - <argument name="orderId" value="$grabOrderNumber"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="goToOrderInAdmin"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickOrderRow"/> - <waitForPageLoad stepKey="waitForOrderPageOpened"/> + <comment userInput="BIC workaround" stepKey="filterByOrderId"/> + <comment userInput="BIC workaround" stepKey="clickOrderRow"/> + <comment userInput="BIC workaround" stepKey="waitForOrderPageOpened"/> <!-- Checking the correctness of displayed custom options for user parameters on Order --> @@ -170,7 +173,10 @@ <!-- Go to Customer Order Page and Checking the correctness of displayed custom options for user parameters on Order --> - <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="amOnOrderPage"/> + <actionGroup ref="StorefrontGoToCustomerOrderDetailsPageActionGroup" stepKey="amOnOrderPage"> + <argument name="orderId" value="$orderId"/> + <argument name="orderNumber" value="$orderNumber"/> + </actionGroup> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($$createProduct.name$$, ProductOptionField.title, ProductOptionField.title)}}" userInput="{{ProductOptionField.title}}" stepKey="seeStorefontOrderProductOptionField1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($$createProduct.name$$, ProductOptionArea.title, ProductOptionArea.title)}}" userInput="{{ProductOptionArea.title}}" stepKey="seeStorefontOrderProductOptionArea1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRemoveProductFromCompareSidebarTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRemoveProductFromCompareSidebarTest.xml index 914ac3444db22..e19446c157605 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRemoveProductFromCompareSidebarTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRemoveProductFromCompareSidebarTest.xml @@ -22,6 +22,9 @@ <createData entity="SimpleProduct" stepKey="simpleProduct"> <requiredEntity createDataKey="defaultCategory"/> </createData> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> </before> <after> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php index 3628494269b59..c0c2452a2a91e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php @@ -17,6 +17,7 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Write; use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Image\Factory; use Magento\Framework\Model\Context; use Magento\Framework\Serialize\SerializerInterface; @@ -140,7 +141,7 @@ protected function setUp(): void $this->mediaDirectory = $this->getMockBuilder(Write::class) ->disableOriginalConstructor() - ->onlyMethods(['create', 'isFile', 'isExist', 'getAbsolutePath']) + ->onlyMethods(['create', 'isFile', 'isExist', 'getAbsolutePath', 'isDirectory', 'getDriver', 'delete']) ->getMock(); $this->filesystem = $this->createMock(Filesystem::class); @@ -503,15 +504,56 @@ public function testIsCached(): void } /** + * @param bool $isRenameSuccessful + * @param string $expectedDirectoryToDelete * @return void - */ - public function testClearCache(): void - { + * @throws \Magento\Framework\Exception\FileSystemException + * @dataProvider clearCacheDataProvider + */ + public function testClearCache( + bool $isRenameSuccessful, + string $expectedDirectoryToDelete + ): void { + $driver = $this->createMock(DriverInterface::class); + $this->mediaDirectory->method('getAbsolutePath') + ->willReturnCallback( + function (string $path) { + return 'path/to/media/' . $path; + } + ); + $this->mediaDirectory->expects($this->exactly(2)) + ->method('isDirectory') + ->willReturnOnConsecutiveCalls(false, true); + $this->mediaDirectory->expects($this->once()) + ->method('getDriver') + ->willReturn($driver); + $driver->expects($this->once()) + ->method('rename') + ->with( + 'path/to/media/catalog/product/cache', + $this->matchesRegularExpression('/^path\/to\/media\/catalog\/product\/\.[0-9A-ZA-z-_]{3}$/') + ) + ->willReturn($isRenameSuccessful); + $this->mediaDirectory->expects($this->once()) + ->method('delete') + ->with($this->matchesRegularExpression($expectedDirectoryToDelete)); + $this->coreFileHelper->expects($this->once())->method('deleteFolder')->willReturn(true); $this->cacheManager->expects($this->once())->method('clean'); $this->image->clearCache(); } + /** + * @return array + */ + public function clearCacheDataProvider(): array + { + return [ + [true, '/^catalog\/product\/\.[0-9A-ZA-z-_]{3}$/'], + [false, '/^catalog\/product\/cache$/'], + ]; + } + /** * @return void */ diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 883e6e763d38e..eb826f77f47a4 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index d72c06022791e..e817bcbb42d25 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -76,6 +76,7 @@ <preference for="Magento\Catalog\Api\Data\MassActionInterface" type="Magento\Catalog\Model\MassAction" /> <preference for="Magento\Catalog\Model\ProductLink\Data\ListCriteriaInterface" type="Magento\Catalog\Model\ProductLink\Data\ListCriteria" /> <preference for="Magento\Catalog\Api\CategoryListDeleteBySkuInterface" type="Magento\Catalog\Model\CategoryLinkRepository"/> + <preference for="Magento\Theme\CustomerData\MessagesProviderInterface" type="Magento\Catalog\Model\Theme\CustomerData\MessagesProvider"/> <type name="Magento\Customer\Model\ResourceModel\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml index 85a6b347f2feb..a5d8a9db97007 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml @@ -9,7 +9,7 @@ <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="manage-titles-wrapper"> <div class="fieldset-wrapper-title"> - <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#manage-titles-content"> + <strong class="admin__collapsible-title" data-bs-toggle="collapse" data-bs-target="#manage-titles-content"> <span><?= $block->escapeHtml(__('Manage Titles (Size, Color, etc.)')) ?></span> </strong> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml index 6848e6f269bc0..1bfa5d4c68443 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml @@ -16,8 +16,8 @@ <div class="fieldset-wrapper-title"> <strong class="admin__collapsible-title" - data-toggle="collapse" - data-target="#<%- data.id %>-content"> + data-bs-toggle="collapse" + data-bs-target="#<%- data.id %>-content"> <span id="option_<%- data.id %>_header_title"><%- data.title %></span> </strong> <div class="actions"> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/custom-options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/custom-options.js index 56713c0c6f300..6105291ec82a3 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/custom-options.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/custom-options.js @@ -177,7 +177,7 @@ define([ 'products[]': request, 'form_key': widget.options.formKey }, function ($data) { - $.parseJSON($data).each(function (el) { + $.each(JSON.parse($data), function (el) { var i; el.id = widget.getFreeOptionId(el.id); diff --git a/app/code/Magento/Catalog/view/frontend/web/js/storage-manager.js b/app/code/Magento/Catalog/view/frontend/web/js/storage-manager.js index e3f5e04bdcb1b..886963db67480 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/storage-manager.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/storage-manager.js @@ -233,6 +233,7 @@ define([ delete params.typeId; delete params.url; + this.requestSent = 1; return utils.ajaxSubmit({ url: url, diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json index 21c8f9be01dd2..9556c28820b62 100644 --- a/app/code/Magento/CatalogAnalytics/composer.json +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/CatalogCmsGraphQl/composer.json b/app/code/Magento/CatalogCmsGraphQl/composer.json index f7ef342c06520..1a245039b241a 100644 --- a/app/code/Magento/CatalogCmsGraphQl/composer.json +++ b/app/code/Magento/CatalogCmsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-cms-graph-ql": "*" diff --git a/app/code/Magento/CatalogCustomerGraphQl/composer.json b/app/code/Magento/CatalogCustomerGraphQl/composer.json index baa393009ae19..f824dcb9e658c 100644 --- a/app/code/Magento/CatalogCustomerGraphQl/composer.json +++ b/app/code/Magento/CatalogCustomerGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index fe7725da5d2b9..99e847b6d1745 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-eav": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php index b2eca68db4d1c..d8415591cb163 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php @@ -205,7 +205,7 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData) return $valid; } - if (!strlen(trim($rowData[$attrCode]))) { + if ($rowData[$attrCode] === null || trim($rowData[$attrCode]) === '') { return true; } @@ -213,6 +213,7 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData) return true; } + $valid = false; switch ($attrParams['type']) { case 'varchar': case 'text': diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php index d2a0019349ef2..6761663502604 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php @@ -7,6 +7,8 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\ValidatorException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\TargetDirectory; @@ -56,18 +58,14 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader 'png' => 'image/png', ]; - const DEFAULT_FILE_TYPE = 'application/octet-stream'; + public const DEFAULT_FILE_TYPE = 'application/octet-stream'; /** - * Image factory. - * * @var \Magento\Framework\Image\AdapterFactory */ protected $_imageFactory; /** - * Validator. - * * @var \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension */ protected $_validator; @@ -114,6 +112,8 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader /** * Directory and filename must be no more than 255 characters in length + * + * @var int */ private $maxFilenameLength = 255; @@ -132,8 +132,8 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader * @param string|null $filePath * @param \Magento\Framework\Math\Random|null $random * @param TargetDirectory|null $targetDirectory - * @throws \Magento\Framework\Exception\FileSystemException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws FileSystemException + * @throws LocalizedException */ public function __construct( \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDb, @@ -182,7 +182,7 @@ public function init() * @param string $fileName * @param bool $renameFileOff * @return array - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function move($fileName, $renameFileOff = false) { @@ -197,17 +197,20 @@ public function move($fileName, $renameFileOff = false) } $this->_setUploadFile($tmpFilePath); - $rootDirectory = $this->getTargetDirectory()->getDirectoryRead(DirectoryList::ROOT); + $rootDirectory = $this->targetDirectory->getDirectoryRead(DirectoryList::ROOT); $destDir = $rootDirectory->getAbsolutePath($this->getDestDir()); $result = $this->save($destDir); - unset($result['path']); - $result['name'] = self::getCorrectFileName($result['name']); - // Directory and filename must be no more than 255 characters in length - if (strlen($result['file']) > $this->maxFilenameLength) { - throw new \LengthException( - __('Filename is too long; must be %1 characters or less', $this->maxFilenameLength) - ); + if (\is_array($result)) { + unset($result['path']); + $result['name'] = self::getCorrectFileName($result['name']); + + // Directory and filename must be no more than 255 characters in length + if (strlen($result['file']) > $this->maxFilenameLength) { + throw new \LengthException( + __('Filename is too long; must be %1 characters or less', $this->maxFilenameLength) + ); + } } return $result; @@ -219,29 +222,30 @@ public function move($fileName, $renameFileOff = false) * @param string $url * @param string $driver * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ private function downloadFileFromUrl($url, $driver) { $parsedUrlPath = parse_url($url, PHP_URL_PATH); + if (!$parsedUrlPath) { - throw new \Magento\Framework\Exception\LocalizedException(__('Could not parse resource url.')); + throw new LocalizedException(__('Could not parse resource url.')); } $urlPathValues = explode('/', $parsedUrlPath); $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', end($urlPathValues)); - + //phpcs:ignore Magento2.Functions.DiscouragedFunction $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION); + if ($fileExtension && !$this->checkAllowedExtension($fileExtension)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.')); + throw new LocalizedException(__('Disallowed file type.')); } - $tmpFileName = str_replace(".$fileExtension", '', $fileName); $tmpFileName .= '_' . $this->random->getRandomString(16); $tmpFileName .= $fileExtension ? ".$fileExtension" : ''; $tmpFilePath = $this->_directory->getRelativePath($this->getTempFilePath($tmpFileName)); if (!$this->_directory->isWritable($this->getTmpDir())) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Import images directory must be writable in order to process remote images.') ); } @@ -253,26 +257,12 @@ private function downloadFileFromUrl($url, $driver) return $tmpFilePath; } - /** - * Retrieves target directory. - * - * @return TargetDirectory - */ - private function getTargetDirectory(): TargetDirectory - { - if (!isset($this->targetDirectory)) { - $this->targetDirectory = ObjectManager::getInstance()->get(TargetDirectory::class); - } - - return $this->targetDirectory; - } - /** * Prepare information about the file for moving * * @param string $filePath * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _setUploadFile($filePath) { @@ -290,7 +280,7 @@ protected function _setUploadFile($filePath) $readable = false; } if (!$readable) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('File \'%1\' was not found or has read restriction.', $filePath) ); } @@ -322,7 +312,7 @@ protected function _readFileInfo($filePath) * Validate uploaded file by type and etc. * * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _validateFile() { @@ -335,7 +325,7 @@ protected function _validateFile() $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION); if (!$this->checkAllowedExtension($fileExtension)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.')); + throw new LocalizedException(__('Disallowed file type.')); } //run validate callbacks foreach ($this->_validateCallbacks as $params) { @@ -405,7 +395,7 @@ public function getDestDir() */ public function setDestDir($path) { - $directoryRoot = $this->getTargetDirectory()->getDirectoryWrite(DirectoryList::ROOT); + $directoryRoot = $this->targetDirectory->getDirectoryWrite(DirectoryList::ROOT); if (is_string($path) && $directoryRoot->isWritable($path)) { $this->_destDir = $path; return true; @@ -429,7 +419,7 @@ protected function _moveFile($tmpPath, $destPath) $destinationRealPath = $this->_directory->getDriver()->getRealPath($destPath); $relativeDestPath = $this->_directory->getRelativePath($destPath); $isSameFile = $tmpRealPath === $destinationRealPath; - $rootDirectory = $this->getTargetDirectory()->getDirectoryWrite(DirectoryList::ROOT); + $rootDirectory = $this->targetDirectory->getDirectoryWrite(DirectoryList::ROOT); return $isSameFile ?: $this->_directory->copyFile($tmpPath, $relativeDestPath, $rootDirectory); } else { return false; diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index 663c207f5790b..0efd8e8b36f1c 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-ctype": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index b0cfa00e36059..e3cdc9e014c90 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*", diff --git a/app/code/Magento/CatalogInventoryGraphQl/composer.json b/app/code/Magento/CatalogInventoryGraphQl/composer.json index d0e015d389f98..5c9e8f55c000b 100644 --- a/app/code/Magento/CatalogInventoryGraphQl/composer.json +++ b/app/code/Magento/CatalogInventoryGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php index 9d1a23611dc27..7d9553c9cc02f 100644 --- a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php +++ b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php @@ -5,15 +5,15 @@ */ namespace Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Tab; -use Magento\Backend\Block\Widget\Form; use Magento\Backend\Block\Widget\Form\Generic; +use Magento\Backend\Block\Widget\Form\Renderer\Fieldset; use Magento\Ui\Component\Layout\Tabs\TabInterface; use Magento\Rule\Model\Condition\AbstractCondition; class Conditions extends Generic implements TabInterface { /** - * @var \Magento\Backend\Block\Widget\Form\Renderer\Fieldset + * @var Fieldset */ protected $_rendererFieldset; @@ -27,7 +27,7 @@ class Conditions extends Generic implements TabInterface * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Data\FormFactory $formFactory * @param \Magento\Rule\Block\Conditions $conditions - * @param \Magento\Backend\Block\Widget\Form\Renderer\Fieldset $rendererFieldset + * @param Fieldset $rendererFieldset * @param array $data */ public function __construct( @@ -35,7 +35,7 @@ public function __construct( \Magento\Framework\Registry $registry, \Magento\Framework\Data\FormFactory $formFactory, \Magento\Rule\Block\Conditions $conditions, - \Magento\Backend\Block\Widget\Form\Renderer\Fieldset $rendererFieldset, + Fieldset $rendererFieldset, array $data = [] ) { $this->_rendererFieldset = $rendererFieldset; @@ -121,7 +121,7 @@ public function isAjaxLoaded() } /** - * @return Form + * @inheritdoc */ protected function _prepareForm() { @@ -135,6 +135,8 @@ protected function _prepareForm() } /** + * Adds 'Conditions' to the form. + * * @param \Magento\CatalogRule\Api\Data\RuleInterface $model * @param string $fieldsetId * @param string $formName @@ -154,7 +156,8 @@ protected function addTabToForm($model, $fieldsetId = 'conditions_fieldset', $fo ['form_namespace' => $formName] ); - $renderer = $this->_rendererFieldset->setTemplate('Magento_CatalogRule::promo/fieldset.phtml') + $renderer = $this->getLayout()->createBlock(Fieldset::class); + $renderer->setTemplate('Magento_CatalogRule::promo/fieldset.phtml') ->setNewChildUrl($newChildUrl) ->setFieldSetId($conditionsFieldSetId); @@ -183,6 +186,8 @@ protected function addTabToForm($model, $fieldsetId = 'conditions_fieldset', $fo } /** + * Sets form name for Condition section. + * * @param AbstractCondition $conditions * @param string $formName * @param string $jsFormName diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection/AdminCatalogPriceRuleGridSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection/AdminCatalogPriceRuleGridSection.xml index aeab4eb369b02..9fc5a364c27c1 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection/AdminCatalogPriceRuleGridSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection/AdminCatalogPriceRuleGridSection.xml @@ -15,5 +15,6 @@ <element name="search" type="button" selector="//div[@id='promo_catalog_grid']//button[@title='Search']" timeout="30"/> <element name="selectRowByRuleName" type="text" selector="//tr[@data-role='row']//td[contains(.,'{{ruleName}}')]" parameterized="true"/> <element name="firstRow" type="text" selector="//tr[@data-role='row']"/> + <element name="firstNotEmptyRow" type="text" selector="tr[data-role='row']:not([class*='no-data'])"/> </section> </sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml index 843bc4e722e65..ff04eded2f884 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml @@ -36,7 +36,7 @@ </before> <after> <!-- delete the simple product and catalog price rule and logout --> - <amOnPage stepKey="goToPriceRulePage" url="admin/catalog_rule/promo_catalog/"/> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> <argument name="name" value="{{_defaultCatalogRule.name}}"/> <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml index 95832cb38a834..ce8d2dd1507fb 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml @@ -35,6 +35,10 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{CustomerEntityOne.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml index 333c4ab06aad1..58e266a0cb544 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -48,8 +48,18 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> - <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToCatalogRuleGridPage"/> - <waitForPageLoad stepKey="waitForCatalogRuleGridPageLoaded"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> + <actionGroup ref="AdminOpenCatalogPriceRulePageActionGroup" stepKey="goToCatalogRuleGridPage"/> + <helper class="Magento\CatalogRule\Test\Mftf\Helper\CatalogPriceRuleHelper" method="deleteAllCatalogPriceRules" stepKey="deleteAllCatalogPriceRules"> + <argument name="firstNotEmptyRow">{{AdminCatalogPriceRuleGrid.firstNotEmptyRow}}</argument> + <argument name="modalAcceptButton">{{AdminConfirmationModalSection.ok}}</argument> + <argument name="deleteButton">{{AdminNewCatalogPriceRule.delete}}</argument> + <argument name="successMessageContainer">{{AdminMessagesSection.success}}</argument> + <argument name="successMessage">You deleted the rule.</argument> + </helper> + <comment userInput="BIC workaround" stepKey="waitForCatalogRuleGridPageLoaded"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearCatalogRuleGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 7ebf62fd445cc..ab9e27c06d991 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php index d27c424ed9ea3..231696f259f59 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php @@ -42,13 +42,26 @@ public function __construct( } /** + * Match configurable child products if configurable product match the condition + * * @param \Magento\CatalogRule\Model\Rule $rule - * @param array $productIds + * @param \Closure $proceed * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function afterGetMatchingProductIds(\Magento\CatalogRule\Model\Rule $rule, array $productIds) - { + public function aroundGetMatchingProductIds( + \Magento\CatalogRule\Model\Rule $rule, + \Closure $proceed + ) { + $productsFilter = $rule->getProductsFilter() ? (array) $rule->getProductsFilter() : []; + if ($productsFilter) { + $parentProductIds = $this->configurable->getParentIdsByChild($productsFilter); + $rule->setProductsFilter(array_unique(array_merge($productsFilter, $parentProductIds))); + } + + $productIds = $proceed(); + $configurableProductIds = $this->configurableProductsProvider->getIds(array_keys($productIds)); foreach ($configurableProductIds as $productId) { if (!isset($this->childrenProducts[$productId])) { @@ -58,11 +71,15 @@ public function afterGetMatchingProductIds(\Magento\CatalogRule\Model\Rule $rule $parentValidationResult = isset($productIds[$productId]) ? array_filter($productIds[$productId]) : []; + $processAllChildren = !$productsFilter || in_array($productId, $productsFilter); foreach ($subProductIds as $subProductId) { - $childValidationResult = isset($productIds[$subProductId]) - ? array_filter($productIds[$subProductId]) - : []; - $productIds[$subProductId] = $parentValidationResult + $childValidationResult; + if ($processAllChildren || in_array($subProductId, $productsFilter)) { + $childValidationResult = isset($productIds[$subProductId]) + ? array_filter($productIds[$subProductId]) + : []; + $productIds[$subProductId] = $parentValidationResult + $childValidationResult; + } + } unset($productIds[$productId]); } diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml index ca9017b7c5f29..a66d9b41f0f8a 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -175,7 +175,9 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindexInvalidatedIndices"> + <argument name="indices" value=""/> + </actionGroup> </after> <!-- Create catalog price rule --> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php index 4d508853643ee..77c9904a22c6b 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php @@ -44,7 +44,7 @@ protected function setUp(): void { $this->configurableMock = $this->createPartialMock( Configurable::class, - ['getChildrenIds'] + ['getChildrenIds', 'getParentIdsByChild'] ); $this->configurableProductsProviderMock = $this->createPartialMock( ConfigurableProductsProvider::class, @@ -61,22 +61,29 @@ protected function setUp(): void /** * @return void */ - public function testAfterGetMatchingProductIdsWithSimpleProduct() + public function testAroundGetMatchingProductIdsWithSimpleProduct() { $this->configurableProductsProviderMock->expects($this->once())->method('getIds')->willReturn([]); $this->configurableMock->expects($this->never())->method('getChildrenIds'); + $this->ruleMock->expects($this->never()) + ->method('setProductsFilter'); $productIds = ['product' => 'valid results']; $this->assertEquals( $productIds, - $this->configurableProductHandler->afterGetMatchingProductIds($this->ruleMock, $productIds) + $this->configurableProductHandler->aroundGetMatchingProductIds( + $this->ruleMock, + function () { + return ['product' => 'valid results']; + } + ) ); } /** * @return void */ - public function testAfterGetMatchingProductIdsWithConfigurableProduct() + public function testAroundGetMatchingProductIdsWithConfigurableProduct() { $this->configurableProductsProviderMock->expects($this->once())->method('getIds') ->willReturn(['conf1', 'conf2']); @@ -84,6 +91,8 @@ public function testAfterGetMatchingProductIdsWithConfigurableProduct() ['conf1', true, [ 0 => ['simple1']]], ['conf2', true, [ 0 => ['simple1', 'simple2']]], ]); + $this->ruleMock->expects($this->never()) + ->method('setProductsFilter'); $this->assertEquals( [ @@ -96,21 +105,118 @@ public function testAfterGetMatchingProductIdsWithConfigurableProduct() 3 => true, ] ], - $this->configurableProductHandler->afterGetMatchingProductIds( + $this->configurableProductHandler->aroundGetMatchingProductIds( $this->ruleMock, - [ - 'conf1' => [ - 0 => true, - 1 => true, - ], - 'conf2' => [ - 0 => false, - 1 => false, - 3 => true, - 4 => false, - ], - ] + function () { + return [ + 'conf1' => [ + 0 => true, + 1 => true, + ], + 'conf2' => [ + 0 => false, + 1 => false, + 3 => true, + 4 => false, + ], + ]; + } ) ); } + + /** + * @param array $productsFilter + * @param array $expectedProductsFilter + * @param array $matchingProductIds + * @param array $expectedMatchingProductIds + * @return void + * @dataProvider aroundGetMatchingProductIdsDataProvider + */ + public function testAroundGetMatchingProductIdsWithProductsFilter( + array $productsFilter, + array $expectedProductsFilter, + array $matchingProductIds, + array $expectedMatchingProductIds + ): void { + $configurableProducts = [ + 'conf1' => ['simple11', 'simple12'], + 'conf2' => ['simple21', 'simple22'], + ]; + $this->configurableProductsProviderMock->method('getIds') + ->willReturnCallback( + function ($ids) use ($configurableProducts) { + return array_intersect($ids, array_keys($configurableProducts)); + } + ); + $this->configurableMock->method('getChildrenIds') + ->willReturnCallback( + function ($id) use ($configurableProducts) { + return [0 => $configurableProducts[$id] ?? []]; + } + ); + + $this->configurableMock->method('getParentIdsByChild') + ->willReturnCallback( + function ($ids) use ($configurableProducts) { + $result = []; + foreach ($configurableProducts as $configurableProduct => $childProducts) { + if (array_intersect($ids, $childProducts)) { + $result[] = $configurableProduct; + } + } + return $result; + } + ); + + $this->ruleMock->method('getProductsFilter') + ->willReturn($productsFilter); + + $this->ruleMock->expects($this->once()) + ->method('setProductsFilter') + ->willReturn($expectedProductsFilter); + + $this->assertEquals( + $expectedMatchingProductIds, + $this->configurableProductHandler->aroundGetMatchingProductIds( + $this->ruleMock, + function () use ($matchingProductIds) { + return $matchingProductIds; + } + ) + ); + } + + /** + * @return array[] + */ + public function aroundGetMatchingProductIdsDataProvider(): array + { + return [ + [ + ['simple1',], + ['simple1',], + ['simple1' => [1 => false]], + ['simple1' => [1 => false],], + ], + [ + ['simple11',], + ['simple11', 'conf1',], + ['simple11' => [1 => false], 'conf1' => [1 => true],], + ['simple11' => [1 => true],], + ], + [ + ['simple11', 'simple12',], + ['simple11', 'conf1',], + ['simple11' => [1 => false], 'conf1' => [1 => true],], + ['simple11' => [1 => true], 'simple12' => [1 => true],], + ], + [ + ['conf1', 'simple11', 'simple12'], + ['conf1', 'simple11', 'simple12'], + ['conf1' => [1 => true], 'simple11' => [1 => false], 'simple12' => [1 => false]], + ['simple11' => [1 => true], 'simple12' => [1 => true]], + ], + ]; + } } diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index c427abc2d86fc..0c75f21ed9abc 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRuleGraphQl/composer.json b/app/code/Magento/CatalogRuleGraphQl/composer.json index 8d9514a0c2670..8527d9d49eb03 100644 --- a/app/code/Magento/CatalogRuleGraphQl/composer.json +++ b/app/code/Magento/CatalogRuleGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 06dcc69ef60f5..9b66606d37a9e 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -177,8 +177,8 @@ public function __construct( TotalRecordsResolverFactory $totalRecordsResolverFactory = null, DefaultFilterStrategyApplyCheckerInterface $defaultFilterStrategyApplyChecker = null ) { - $this->searchResultFactory = $searchResultFactory ?? \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Api\Search\SearchResultFactory::class); + $this->searchResultFactory = $searchResultFactory + ?? ObjectManager::getInstance()->get(SearchResultFactory::class); parent::__construct( $entityFactory, $logger, @@ -368,13 +368,12 @@ public function _loadEntities($printQuery = false, $logQuery = false) $this->getEntity(); $this->printLogQuery($printQuery, $logQuery); - + /** + * Prepare select query + * @var string $query + */ + $query = $this->getSelect(); try { - /** - * Prepare select query - * @var string $query - */ - $query = $this->getSelect(); $rows = $this->_fetchAll($query); } catch (\Exception $e) { $this->printLogQuery(false, true, $query); @@ -455,7 +454,7 @@ protected function _renderFiltersBefore() } if ($this->searchRequestName !== 'quick_search_container' - || strlen(trim($this->queryText)) + || ($this->queryText && strlen(trim($this->queryText))) ) { $this->prepareSearchTermFilter(); $this->preparePriceAggregation(); diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml index 1a024b32ce7f2..1456ad4c80fce 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml @@ -43,6 +43,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToFrontPage"/> diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index ccea24a0010a9..19cc6b447d804 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml index 5fbf9e474e232..469b699df52b8 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml @@ -14,5 +14,6 @@ <data key="urlRest">URL key "rest" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key.</data> <data key="urlGraphql">URL key "graphql" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key.</data> <data key="urlAdminError">URL key "admin" matches a reserved endpoint name (admin). Use another URL key.</data> + <data key="urlAdminPartialError">URL key "admin" matches a reserved endpoint name </data> </entity> </entities> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml index 57ed644d1a926..e1b59c07d187a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml @@ -46,12 +46,12 @@ <argument name="categoryName" value="admin"/> <argument name="categoryUrlKey" value=""/> </actionGroup> - <see selector="{{AdminMessagesSection.error}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdminError}}' stepKey="seeAdminFirstErrorMessage"/> + <see selector="{{AdminMessagesSection.error}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdminPartialError}}' stepKey="seeAdminFirstErrorMessage"/> <actionGroup ref="FillCategoryNameAndUrlKeyAndSaveActionGroup" stepKey="fillAdminSecondCategoryForm"> <argument name="categoryName" value="{{SimpleSubCategory.name}}"/> <argument name="categoryUrlKey" value="admin"/> </actionGroup> - <see selector="{{AdminMessagesSection.error}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdminError}}' stepKey="seeAdminSecondErrorMessage"/> + <see selector="{{AdminMessagesSection.error}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdminPartialError}}' stepKey="seeAdminSecondErrorMessage"/> <!--Create category with 'admin' name--> <comment userInput="Create category with 'admin' name" stepKey="commentAdminCategoryCreation"/> <actionGroup ref="FillCategoryNameAndUrlKeyAndSaveActionGroup" stepKey="fillAdminThirdCategoryForm"> diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index 822d2674ac559..f50d88712f8ce 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json index 797d970c6c95a..3cb292c59e6a5 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index e1756b9bd2cf7..e2ca79001d9c4 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Checkout/Model/TotalsInformationManagement.php b/app/code/Magento/Checkout/Model/TotalsInformationManagement.php index 7328f8845545c..25e2f0ba4e005 100644 --- a/app/code/Magento/Checkout/Model/TotalsInformationManagement.php +++ b/app/code/Magento/Checkout/Model/TotalsInformationManagement.php @@ -5,14 +5,14 @@ */ namespace Magento\Checkout\Model; +use Magento\Checkout\Api\Data\TotalsInformationInterface; + /** * Class for management of totals information. */ class TotalsInformationManagement implements \Magento\Checkout\Api\TotalsInformationManagementInterface { /** - * Cart total repository. - * * @var \Magento\Quote\Api\CartTotalRepositoryInterface */ protected $cartTotalRepository; @@ -42,7 +42,7 @@ public function __construct( */ public function calculate( $cartId, - \Magento\Checkout\Api\Data\TotalsInformationInterface $addressInformation + TotalsInformationInterface $addressInformation ) { /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->cartRepository->get($cartId); @@ -53,9 +53,19 @@ public function calculate( } else { $quote->setShippingAddress($addressInformation->getAddress()); if ($addressInformation->getShippingCarrierCode() && $addressInformation->getShippingMethodCode()) { - $quote->getShippingAddress()->setCollectShippingRates(true)->setShippingMethod( - $addressInformation->getShippingCarrierCode().'_'.$addressInformation->getShippingMethodCode() + $shippingMethod = implode( + '_', + [$addressInformation->getShippingCarrierCode(), $addressInformation->getShippingMethodCode()] ); + $quoteShippingAddress = $quote->getShippingAddress(); + if ($quoteShippingAddress->getShippingMethod() && + $quoteShippingAddress->getShippingMethod() !== $shippingMethod + ) { + $quoteShippingAddress->setShippingAmount(0); + $quoteShippingAddress->setBaseShippingAmount(0); + } + $quoteShippingAddress->setCollectShippingRates(true) + ->setShippingMethod($shippingMethod); } } $quote->collectTotals(); diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest/CheckCheckoutSuccessPageAsRegisterCustomerTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest/CheckCheckoutSuccessPageAsRegisterCustomerTest.xml index 2c878357ca261..0993f8ba6fa7e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest/CheckCheckoutSuccessPageAsRegisterCustomerTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest/CheckCheckoutSuccessPageAsRegisterCustomerTest.xml @@ -15,9 +15,6 @@ <description value="To be sure that other elements of Success page are shown for placed order as registered Customer."/> <severity value="CRITICAL"/> <testCaseId value="MC-16488"/> - <skip> - <issueId value="MQE-2834" /> - </skip> <group value="checkout"/> <group value="pr_exclude"/> </annotations> @@ -30,7 +27,7 @@ </before> <after> - <!--Cancel orders--> + <!-- Cancel orders --> <actionGroup ref="AdminLoginActionGroup" stepKey="adminLogin"/> <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="goToOrdersPage"/> <actionGroup ref="AdminGridColumnShowActionGroup" stepKey="showCustomerEmailColumn"> @@ -46,35 +43,35 @@ </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> - <!--Logout from customer account--> + <!-- Logout from customer account --> <amOnPage url="{{StorefrontCustomerLogoutPage.url}}" stepKey="logoutCustomerOne"/> <waitForPageLoad stepKey="waitLogoutCustomerOne"/> <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createSimpleUsCustomer" stepKey="deleteCustomer"/> </after> - <!--Log in to Storefront as Customer--> + <!-- Log in to Storefront as Customer --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUpNewUser"> <argument name="Customer" value="$$createSimpleUsCustomer$$"/> </actionGroup> - <!--Go to product page--> + <!-- Go to product page --> <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage"/> <waitForPageLoad stepKey="waitForCatalogPageLoad"/> - <!--Add Product to Shopping Cart--> + <!-- Add Product to Shopping Cart --> <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"> <argument name="productName" value="$$createSimpleProduct.name$$"/> </actionGroup> - <!--Go to Checkout--> + <!-- Go to Checkout --> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> <actionGroup ref="StorefrontSelectFirstShippingMethodActionGroup" stepKey="selectFirstShippingMethod"/> <comment userInput="Adding the comment to replace waitForLoadingMask2 action for preserving Backward Compatibility" stepKey="waitForLoadingMask2"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> <!-- Checkout select Check/Money Order payment --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> - <!--Click Place Order button--> + <!-- Click Place Order button --> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSuccessTitle"/> <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order number is: " stepKey="seeOrderNumber"/> @@ -83,16 +80,16 @@ <click selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="clickOrderLink"/> <seeInCurrentUrl url="{{StorefrontCustomerOrderPage.url}}" stepKey="seeMyOrderPage"/> - <!--Go to product page--> + <!-- Go to product page --> <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage2"/> <waitForPageLoad stepKey="waitForCatalogPageLoad2"/> - <!--Add Product to Shopping Cart--> + <!-- Add Product to Shopping Cart --> <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage2"> <argument name="productName" value="$$createSimpleProduct.name$$"/> </actionGroup> - <!--Go to Checkout--> + <!-- Go to Checkout --> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2"/> <actionGroup ref="StorefrontSelectFirstShippingMethodActionGroup" stepKey="selectFirstShippingMethod2"/> <comment userInput="Adding the comment to replace waitForLoadingMask3 action for preserving Backward Compatibility" stepKey="waitForLoadingMask3"/> @@ -100,23 +97,23 @@ <!-- Checkout select Check/Money Order payment --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> - <!--Click Place Order button--> + <!-- Click Place Order button --> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder2"/> <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage2"/> <click selector="{{CheckoutSuccessMainSection.continueShoppingButton}}" stepKey="clickContinueShoppingButton"/> <see userInput="Home Page" selector="{{StorefrontCMSPageSection.mainTitle}}" stepKey="seeHomePageTitle"/> <seeCurrentUrlEquals url="{{_ENV.MAGENTO_BASE_URL}}" stepKey="seeHomePageUrl"/> - <!--Go to product page--> + <!-- Go to product page --> <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage3"/> <waitForPageLoad stepKey="waitForCatalogPageLoad3"/> - <!--Add Product to Shopping Cart--> + <!-- Add Product to Shopping Cart --> <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage3"> <argument name="productName" value="$$createSimpleProduct.name$$"/> </actionGroup> - <!--Go to Checkout--> + <!-- Go to Checkout --> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart3"/> <actionGroup ref="StorefrontSelectFirstShippingMethodActionGroup" stepKey="selectFirstShippingMethod3"/> <comment userInput="Adding the comment to replace waitForLoadingMask4 action for preserving Backward Compatibility" stepKey="waitForLoadingMask4"/> @@ -125,11 +122,11 @@ <!-- Checkout select Check/Money Order payment --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment3"/> - <!--Click Place Order button--> + <!-- Click Place Order button --> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder3"/> <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage3"/> - <!--Check "Print Receipt" button is presented (desktop only)--> + <!-- Check "Print Receipt" button is presented (desktop only) --> <seeElement selector="{{CheckoutSuccessMainSection.printLink}}" stepKey="seeVisiblePrint"/> <resizeWindow width="600" height="800" stepKey="resizeWindow"/> <waitForElementNotVisible selector="{{CheckoutSuccessMainSection.printLink}}" stepKey="waitInvisiblePrint"/> @@ -138,11 +135,16 @@ <waitForElementVisible selector="{{CheckoutSuccessMainSection.printLink}}" stepKey="waitVisiblePrint"/> <seeElement selector="{{CheckoutSuccessMainSection.printLink}}" stepKey="seeVisiblePrint2"/> - <!--See print page--> + <!-- See print page --> <click selector="{{CheckoutSuccessMainSection.printLink}}" stepKey="clickPrintLink"/> - <switchToWindow stepKey="switchToWindow"/> - <switchToNextTab stepKey="switchToTab"/> - <seeInCurrentUrl url="sales/order/print/order_id" stepKey="seePrintPage"/> + <helper class="Magento\Sales\Test\Mftf\Helper\SalesHelper" method="switchToWindowWithUrlAndClosePrintDialogIfEncountered" stepKey="switchToWindowWithUrlAndClosePrintDialogIfEncountered"> + <argument name="expectedUrl">sales/order/print/order_id/</argument> + <argument name="expectedUrlComparisonType">COMPARISON_PATH_SUBSET_MATCH</argument> + </helper> + + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToWindow"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToTab"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="seePrintPage"/> <seeElement selector="{{StorefrontCustomerOrderViewSection.orderTitle}}" stepKey="seeOrderTitleOnPrint"/> <switchToWindow stepKey="switchToWindow2"/> </test> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml index 8b1a1b2a62eb6..64f392d39edcb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml @@ -76,23 +76,25 @@ <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution" /> <!-- Check order summary in checkout --> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderButton"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> - <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="waitForPaymentSectionLoaded"/> + <comment userInput="BIC workaround" stepKey="waitForPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="clickPlaceOrderButton"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <comment userInput="BIC workaround" stepKey="orderIsSuccessfullyPlaced"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <!-- Open created order in backend --> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="entityId" value="$grabOrderId"/> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> - <comment userInput="Comment is added to preserve Backward Compatibility" stepKey="goToOrders"/> - <comment userInput="Comment is added to preserve Backward Compatibility" stepKey="filterOrderGridById"/> - + <!-- Assert order total --> <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$565.00" stepKey="checkOrderTotalInBackend"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml index 7b6b07f467fbb..fb8ae7d5f5746 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml @@ -89,21 +89,19 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForCheckoutPaymentSectionPageLoad"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - - <!--Step11. Go to admin Order page for newly created order--> - <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openAdminOrderViewPage"> - <argument name="entityId" value="$grabOrderId"/> - </actionGroup> <!-- Open created order in backend --> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="goToOrders"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="filterOrderGridById"/> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openAdminOrderViewPage"> + <argument name="entityId" value="{$grabOrderId}"/> + </actionGroup> <!-- Assert order total --> <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index 7301cfb48ccc7..b60b60d5d0dc5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -82,17 +82,18 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForCheckoutPaymentSectionPageLoad"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <!-- Open created order in backend --> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="entityId" value="$grabOrderId"/> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> - <comment userInput="Comment is added to preserve Backward Compatibility" stepKey="goToOrders"/> - <comment userInput="Comment is added to preserve Backward Compatibility" stepKey="filterOrderGridById"/> <!-- Assert order total --> <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml index a8a5e4a4cc5ed..90bec93990b10 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml @@ -86,20 +86,23 @@ <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution" /> <!-- Check order summary in checkout --> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderButton"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> - <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="waitForPaymentSectionLoaded"/> + <comment userInput="BIC workaround" stepKey="waitForPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="clickPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="orderIsSuccessfullyPlaced"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Open created order in backend --> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> - <waitForPageLoad stepKey="waitForOrdersPageLoad"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrderGridById"> - <argument name="orderId" value="$grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="waitForOrdersPageLoad"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrderGridById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Assert order total --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml index dfd4a5deadaf6..b8b8155159d37 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml @@ -70,11 +70,12 @@ <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution" /> <!-- Check order summary in checkout --> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderButton"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> - <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="waitForPaymentSectionLoaded"/> + <comment userInput="BIC workaround" stepKey="waitForPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="clickPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="orderIsSuccessfullyPlaced"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> @@ -82,12 +83,12 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Open created order in backend --> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openAdminOrderViewPage"> - <argument name="entityId" value="$grabOrderId"/> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="goToOrders"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="filterOrderGridById"/> <!-- Assert that shipping and billing address are the same --> <grabTextFrom selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" stepKey="shippingAddress"/> <grabTextFrom selector="{{AdminShipmentAddressInformationSection.billingAddress}}" stepKey="billingAddress"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml index f2ce8bd0eadd2..90bf2c1465e43 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml @@ -191,10 +191,11 @@ <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForShipmentPageLoad"/> <!-- Check order summary in checkout --> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> - <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced" /> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="waitForPaymentSectionLoaded"/> + <comment userInput="BIC workaround" stepKey="clickPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="orderIsSuccessfullyPlaced"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> @@ -202,12 +203,12 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Open created order --> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openAdminOrderViewPage"> - <argument name="entityId" value="$grabOrderId"/> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="goToOrders"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="filterOrderGridById"/> <!-- Assert that addresses on order page the same --> <grabTextFrom selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" stepKey="shippingAddressOrderPage"/> <grabTextFrom selector="{{AdminShipmentAddressInformationSection.billingAddress}}" stepKey="billingAddressOrderPage"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml index f20b2c22f99a2..1462685c3054f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml @@ -58,9 +58,12 @@ <fillField selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}" userInput="{{CustomerEntityOne.password}}" stepKey="TypeConfirmationPassword"/> <click selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" stepKey="clickOnCreateAccount"/> <see userInput="Thank you for registering" stepKey="verifyAccountCreated"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdmin"/> - <amOnPage url="{{AdminOrderPage.url({$grabOrderNumber})}}" stepKey="navigateToOrderPage"/> - <waitForPageLoad stepKey="waitForCreatedOrderPage"/> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="navigateToOrderPage"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForCreatedOrderPage"/> <see stepKey="seeCustomerName" userInput="{{CustomerEntityOne.firstname}}" selector="{{AdminShipmentOrderInformationSection.customerName}}"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml index 4b98a1f177af8..eb76748a81c97 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml @@ -33,6 +33,9 @@ <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> <!-- Delete customer --> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindexCustomerGrid"> + <argument name="indices" value="customer_grid"/> + </actionGroup> <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> <argument name="customerEmail" value="CustomerEntityOne.email"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml index e09c2a3af2f3e..48059ef66d47a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml @@ -75,7 +75,8 @@ <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForPlaceOrderButton"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderNumber"/> <!--Verify New addresses in Customer's Address Book--> <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToCustomerAddressBook"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml index 99a9e0273430d..71e884cc36fb4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml @@ -58,8 +58,10 @@ <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> <!-- Order review page has address that was created during checkout --> - <amOnPage url="{{AdminOrderPage.url({$grabOrderNumber})}}" stepKey="navigateToOrderPage"/> - <waitForPageLoad stepKey="waitForCreatedOrderPage"/> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="navigateToOrderPage"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForCreatedOrderPage"/> <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{CustomerAddressSimple.street[0]}} {{CustomerAddressSimple.city}}, {{CustomerAddressSimple.state}}, {{CustomerAddressSimple.postcode}}" stepKey="checkShippingAddress"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Unit/Model/TotalsInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/TotalsInformationManagementTest.php index 61049b4893476..d6feb38dc6012 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/TotalsInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/TotalsInformationManagementTest.php @@ -12,6 +12,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\CartTotalRepositoryInterface; +use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; class TotalsInformationManagementTest extends \PHPUnit\Framework\TestCase @@ -67,7 +68,7 @@ public function testCalculate(?string $carrierCode, ?string $carrierMethod, int { $cartId = 1; $cartMock = $this->createMock( - \Magento\Quote\Model\Quote::class + Quote::class ); $cartMock->expects($this->once())->method('getItemsCount')->willReturn(1); $cartMock->expects($this->once())->method('getIsVirtual')->willReturn(false); @@ -101,6 +102,72 @@ public function testCalculate(?string $carrierCode, ?string $carrierMethod, int $this->totalsInformationManagement->calculate($cartId, $addressInformationMock); } + /** + * Test case when shipping amount must be reset to 0 because of changed shipping method. + */ + public function testResetShippingAmount() + { + $cartId = 1; + $carrierCode = 'carrier_code'; + $carrierMethod = 'carrier_method'; + + $cartMock = $this->createMock(Quote::class); + $cartMock->method('getItemsCount') + ->willReturn(1); + $cartMock->method('getIsVirtual') + ->willReturn(false); + $this->cartRepositoryMock->method('get')->with($cartId)->willReturn($cartMock); + $this->cartTotalRepositoryMock->method('get')->with($cartId); + + $addressInformationMock = $this->createMock(TotalsInformationInterface::class); + $addressMock = $this->getMockBuilder(Address::class) + ->addMethods( + [ + 'setShippingMethod', + 'setCollectShippingRates' + ] + )->onlyMethods( + [ + 'getShippingMethod', + 'setShippingAmount', + 'setBaseShippingAmount', + ] + ) + ->disableOriginalConstructor() + ->getMock(); + $addressMock->method('getShippingMethod') + ->willReturn('flatrate_flatrate'); + $addressInformationMock->method('getAddress') + ->willReturn($addressMock); + $addressInformationMock->method('getShippingCarrierCode') + ->willReturn($carrierCode); + $addressInformationMock->method('getShippingMethodCode') + ->willReturn($carrierMethod); + $cartMock->method('setShippingAddress') + ->with($addressMock); + $cartMock->method('getShippingAddress') + ->willReturn($addressMock); + $addressMock->expects($this->once()) + ->method('setCollectShippingRates') + ->with(true) + ->willReturn($addressMock); + $addressMock->expects($this->once()) + ->method('setShippingAmount') + ->with(0) + ->willReturn($addressMock); + $addressMock->expects($this->once()) + ->method('setBaseShippingAmount') + ->with(0) + ->willReturn($addressMock); + $addressMock->expects($this->once()) + ->method('setShippingMethod') + ->with($carrierCode . '_' . $carrierMethod); + $cartMock->expects($this->once()) + ->method('collectTotals'); + + $this->totalsInformationManagement->calculate($cartId, $addressInformationMock); + } + /** * Data provider for testCalculate. * diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index fa5dcb5730f4a..753ea5dc0749f 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-captcha": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 765c260ac9d07..b74896972b28c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -42,7 +42,7 @@ <dt class="label"><!-- ko text: option.label --><!-- /ko --></dt> <dd class="values"> <!-- ko if: Array.isArray(option.value) --> - <span data-bind="html: $parents[1].getOptionValueUnsanitizedHtml(option.value.join('<br/>'))"></span> + <span data-bind="html: $parents[1].getOptionValueUnsanitizedHtml(option.value.join('<br>'))"></span> <!-- /ko --> <!-- ko if: (!Array.isArray(option.value) && ['file', 'html'].includes(option.option_type)) --> <span data-bind="html: $parents[1].getOptionValueUnsanitizedHtml(option.value)"></span> @@ -83,7 +83,7 @@ type="number" size="4" class="item-qty cart-item-qty"/> - <button data-bind="attr: { + <button data-bind="attr: { id: 'update-cart-item-'+item_id, 'data-cart-item': item_id, title: $t('Update') diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index 4aaf84041ca14..26416ba52a75a 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json index 2b399e4359866..bdf657fe93fcf 100644 --- a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json +++ b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-checkout-agreements": "*" diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml index 0b46c106f7121..a1a7b3c9af9ec 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml @@ -15,9 +15,14 @@ <arguments> <argument name="UrlKey" type="string"/> </arguments> - + <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnCMSNewPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> + <waitForElementVisible selector="{{AdminDataGridHeaderSection.bookmarkToggle}}" stepKey="waitForViewBookmarks"/> + <click selector="{{AdminDataGridHeaderSection.bookmarkToggle}}" stepKey="openViewBookmarks"/> + <click selector="{{AdminDataGridHeaderSection.bookmarkOption(DefaultGridView.name)}}" stepKey="selectDefaultGridView"/> + <waitForPageLoad stepKey="waitForGridReset"/> + <see selector="{{AdminDataGridHeaderSection.bookmarkToggle}}" userInput="{{DefaultGridView.name}}" stepKey="seeDefaultViewSelected"/> <click selector="{{CmsPagesPageActionsSection.select(UrlKey)}}" stepKey="clickSelect"/> <click selector="{{CmsPagesPageActionsSection.delete(UrlKey)}}" stepKey="clickDelete"/> <waitForElementVisible selector="{{CmsPagesPageActionsSection.deleteConfirm}}" stepKey="waitForOkButtonToBeVisible"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/TinyMCESection.xml index d1c9656751e57..abc4d99e16597 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/TinyMCESection.xml @@ -32,6 +32,7 @@ <element name="WidgetButton" type="button" selector="span[class*='magento-widget mceNonEditable']"/> <element name="EditorContent" type="input" selector="#tinymce"/> <element name="Content" type="textarea" selector="textarea"/> + <element name="ContentEditor" type="textarea" selector="#text"/> <element name="ShowHideBtn" type="button" selector=".scalable.action-show-hide"/> <element name="EditArea" type="text" selector=".tox-edit-area"/> <element name="EditAreaIframe" type="iframe" selector=".tox-edit-area iframe"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index a027252799550..de7c1145b4e6f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -44,8 +44,9 @@ <see selector="{{WidgetSection.InsertWidgetBtnEnabled}}" userInput="Insert Widget" stepKey="seeInsertWidgetEnabled" /> <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Category Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectCategoryBtn" /> - <waitForLoadingMaskToDisappear stepKey="wait3"/> - <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForPageLoad stepKey="wait3"/> + <waitForElementVisible selector="{{AdminCategorySidebarTreeSection.expandCategoryByName('Default Category')}}" stepKey="waitForDefaultCategory"/> + <conditionalClick selector="{{AdminCategorySidebarTreeSection.expandCategoryByName('Default Category')}}" dependentSelector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" visible="false" stepKey="expandRootCategory"/> <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCreateCategory" /> <waitForElementNotVisible selector="{{WidgetSection.SelectCategoryTitle}}" stepKey="waitForSlideoutCloses1" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 52730914cada2..49173a6128548 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -48,8 +48,9 @@ <see selector="{{WidgetSection.InsertWidgetBtnEnabled}}" userInput="Insert Widget" stepKey="seeInsertWidgetEnabled" /> <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Product Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectPageBtn" /> - <waitForLoadingMaskToDisappear stepKey="wait4"/> - <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForPageLoad stepKey="wait4"/> + <waitForElementVisible selector="{{AdminCategorySidebarTreeSection.expandCategoryByName('Default Category')}}" stepKey="waitForDefaultCategory"/> + <conditionalClick selector="{{AdminCategorySidebarTreeSection.expandCategoryByName('Default Category')}}" dependentSelector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" visible="false" stepKey="expandRootCategory"/> <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCategory" /> <waitForLoadingMaskToDisappear stepKey="waitLoadingMask" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml index 1f1f1c98d507b..cb79113fe591c 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml @@ -21,16 +21,19 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <createData entity="_defaultCmsPage" stepKey="createPage"/> + <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="goToCMSPages"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> + <actionGroup ref="AdminOpenDashboardPageActionGroup" stepKey="goToAdminDashboard"/> </before> <after> - <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilter"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="clearGridFilter"/> <deleteData createDataKey="createPage" stepKey="deletePage"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> <amOnPage url="{{CmsPagesPage.url}}?filters[title]=$$createPage.title$$" stepKey="navigateToPageGridWithFilters"/> <waitForPageLoad stepKey="waitForPageGrid"/> - <see selector="{{CmsPagesPageActionsSection.pagesGridRowByTitle($$createPage.title$$)}}" userInput="$$createPage.title$$" stepKey="seePage"/> - <seeElement selector="{{CmsPagesPageActionsSection.activeFilter}}" stepKey="seeEnabledFilters"/> - <see selector="{{CmsPagesPageActionsSection.activeFilter}}" userInput="Title: $$createPage.title$$" stepKey="seePageTitleFilter"/> + <waitForText selector="{{CmsPagesPageActionsSection.pagesGridRowByTitle($$createPage.title$$)}}" userInput="$$createPage.title$$" stepKey="seePage"/> + <waitForElementVisible selector="{{CmsPagesPageActionsSection.activeFilter}}" stepKey="seeEnabledFilters"/> + <waitForText selector="{{CmsPagesPageActionsSection.activeFilter}}" userInput="Title: $$createPage.title$$" stepKey="seePageTitleFilter"/> </test> </tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml index f82ae3eed5de6..3ca6fff1659ad 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml @@ -19,18 +19,23 @@ <group value="Cms"/> </annotations> <before> + <actionGroup ref="AdminDisableWYSIWYGActionGroup" stepKey="disableWYSYWYG"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> - <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> + <comment userInput="BIC workaround" stepKey="disableWYSIWYG"/> </before> <after> + <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="navigateToCmsPageGrid"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="clearGridFilters"/> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePage"> + <argument name="UrlKey" value="{{_defaultCmsPage.identifier}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + <actionGroup ref="AdminEnableWYSIWYGActionGroup" stepKey="enableWYSYWYG"/> </after> - <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="navigateToCmsPageGrid" /> - <actionGroup ref="CreateNewPageWithBasicValues" stepKey="createNewPageWithBasicValues" /> - <actionGroup ref="SaveCmsPageActionGroup" stepKey="clickSaveCmsPageButton" /> - <actionGroup ref="VerifyCreatedCmsPage" stepKey="verifyCmsPage" /> - <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePage"> - <argument name="UrlKey" value="{{_defaultCmsPage.identifier}}"/> - </actionGroup> + <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="navigateToCmsPageGrid"/> + <actionGroup ref="CreateNewPageWithBasicValues" stepKey="createNewPageWithBasicValues"/> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="clickSaveCmsPageButton"/> + <actionGroup ref="VerifyCreatedCmsPage" stepKey="verifyCmsPage"/> + <comment userInput="BIC workaround" stepKey="deletePage"/> </test> </tests> diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index 7dbb31f23e1c6..364c01b97b4c2 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index d4a1461afb0ae..2eac4349fd57f 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-widget": "*", diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index d191c1dc91731..24866def5d263 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json index ced7ed1dc94eb..6e26e2e5c961c 100644 --- a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CompareListGraphQl/composer.json b/app/code/Magento/CompareListGraphQl/composer.json index 9e46815c9ac8c..84aaf385335da 100644 --- a/app/code/Magento/CompareListGraphQl/composer.json +++ b/app/code/Magento/CompareListGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*" diff --git a/app/code/Magento/Config/Console/Command/ConfigShowCommand.php b/app/code/Magento/Config/Console/Command/ConfigShowCommand.php index 53c7445f9508a..445fd8e67937e 100644 --- a/app/code/Magento/Config/Console/Command/ConfigShowCommand.php +++ b/app/code/Magento/Config/Console/Command/ConfigShowCommand.php @@ -31,12 +31,14 @@ class ConfigShowCommand extends Command /**#@+ * Names of input arguments or options. */ - const INPUT_OPTION_SCOPE = 'scope'; - const INPUT_OPTION_SCOPE_CODE = 'scope-code'; - const INPUT_ARGUMENT_PATH = 'path'; + public const INPUT_OPTION_SCOPE = 'scope'; + public const INPUT_OPTION_SCOPE_CODE = 'scope-code'; + public const INPUT_ARGUMENT_PATH = 'path'; /**#@-*/ - /**#@-*/ + /** + * @var ValidatorInterface + */ private $scopeValidator; /** @@ -165,7 +167,8 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $this->scope = $input->getOption(self::INPUT_OPTION_SCOPE); $this->scopeCode = $input->getOption(self::INPUT_OPTION_SCOPE_CODE); - $this->inputPath = trim($input->getArgument(self::INPUT_ARGUMENT_PATH), '/'); + $inputPath = $input->getArgument(self::INPUT_ARGUMENT_PATH); + $this->inputPath = $inputPath !== null ? trim($inputPath, '/') : ''; $configValue = $this->emulatedAreaProcessor->process(function () { $this->scopeValidator->isValid($this->scope, $this->scopeCode); diff --git a/app/code/Magento/Config/Model/Config/Backend/File.php b/app/code/Magento/Config/Model/Config/Backend/File.php index 1d5b26cebcfa5..4a3ed6307cf3c 100644 --- a/app/code/Magento/Config/Model/Config/Backend/File.php +++ b/app/code/Magento/Config/Model/Config/Backend/File.php @@ -5,8 +5,10 @@ */ namespace Magento\Config\Model\Config\Backend; +use Exception; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem; use Magento\MediaStorage\Model\File\Uploader; @@ -82,12 +84,13 @@ public function __construct( * Save uploaded file before saving config value * * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function beforeSave() { $value = $this->getValue(); $file = $this->getFileData(); + if (!empty($file)) { $uploadDir = $this->_getUploadDir(); try { @@ -97,12 +100,11 @@ public function beforeSave() $uploader->setAllowRenameFiles(true); $uploader->addValidateCallback('size', $this, 'validateMaxSize'); $result = $uploader->save($uploadDir); - } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__('%1', $e->getMessage())); + } catch (Exception $e) { + throw new LocalizedException(__('%1', $e->getMessage())); } - - $filename = $result['file']; - if ($filename) { + if ($result !== false) { + $filename = $result['file']; if ($this->_addWhetherScopeInfo()) { $filename = $this->_prependScopeInfo($filename); } @@ -148,7 +150,7 @@ protected function getFileData() * * @param string $filePath Path to temporary uploaded file * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function validateMaxSize($filePath) { @@ -157,7 +159,7 @@ public function validateMaxSize($filePath) $directory->getRelativePath($filePath) )['size'] > $this->_maxFileSize * 1024 ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('The file you\'re uploading exceeds the server size limit of %1 kilobytes.', $this->_maxFileSize) ); } @@ -179,14 +181,14 @@ protected function _addWhetherScopeInfo() * Return path to directory for upload file * * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _getUploadDir() { $fieldConfig = $this->getFieldConfig(); if (!array_key_exists('upload_dir', $fieldConfig)) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('The base directory to upload file is not specified.') ); } diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/Iterator.php b/app/code/Magento/Config/Model/Config/Structure/Element/Iterator.php index f53ecd710b28c..5f85ebcc7d2fa 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Element/Iterator.php +++ b/app/code/Magento/Config/Model/Config/Structure/Element/Iterator.php @@ -69,6 +69,7 @@ public function setElements(array $elements, $scope) * * @return \Magento\Config\Model\Config\StructureElementInterface */ + #[\ReturnTypeWillChange] public function current() { return $this->_flyweight; @@ -79,6 +80,7 @@ public function current() * * @return void Any returned value is ignored. */ + #[\ReturnTypeWillChange] public function next() { next($this->_elements); @@ -106,6 +108,7 @@ protected function _initFlyweight(array $element) * * @return void */ + #[\ReturnTypeWillChange] public function key() { key($this->_elements); @@ -117,6 +120,7 @@ public function key() * @return bool The return value will be casted to boolean and then evaluated. * Returns true on success or false on failure. */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->_elements); @@ -127,6 +131,7 @@ public function valid() * * @return void Any returned value is ignored. */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->_elements); diff --git a/app/code/Magento/Config/Setup/ConfigOptionsList.php b/app/code/Magento/Config/Setup/ConfigOptionsList.php index c410eeae615e5..1703540f0acd5 100644 --- a/app/code/Magento/Config/Setup/ConfigOptionsList.php +++ b/app/code/Magento/Config/Setup/ConfigOptionsList.php @@ -22,22 +22,22 @@ class ConfigOptionsList implements ConfigOptionsListInterface /** * Input key for the debug_logging option. */ - const INPUT_KEY_DEBUG_LOGGING = 'enable-debug-logging'; + public const INPUT_KEY_DEBUG_LOGGING = 'enable-debug-logging'; /** * Path to the debug_logging value in the deployment config. */ - const CONFIG_PATH_DEBUG_LOGGING = 'dev/debug/debug_logging'; + public const CONFIG_PATH_DEBUG_LOGGING = 'dev/debug/debug_logging'; /** * Input key for the syslog_logging option. */ - const INPUT_KEY_SYSLOG_LOGGING = 'enable-syslog-logging'; + public const INPUT_KEY_SYSLOG_LOGGING = 'enable-syslog-logging'; /** * Path to the syslog_logging value in the deployment config. */ - const CONFIG_PATH_SYSLOG_LOGGING = 'dev/syslog/syslog_logging'; + public const CONFIG_PATH_SYSLOG_LOGGING = 'dev/syslog/syslog_logging'; /** * @var ConfigDataFactory @@ -61,14 +61,14 @@ public function getOptions() new SelectConfigOption( self::INPUT_KEY_DEBUG_LOGGING, SelectConfigOption::FRONTEND_WIZARD_RADIO, - [true, false, 1, 0], + ['true', 'false', 1, 0], self::CONFIG_PATH_DEBUG_LOGGING, 'Enable debug logging' ), new SelectConfigOption( self::INPUT_KEY_SYSLOG_LOGGING, SelectConfigOption::FRONTEND_WIZARD_RADIO, - [true, false, 1, 0], + ['true', 'false', 1, 0], self::CONFIG_PATH_SYSLOG_LOGGING, 'Enable syslog logging' ), diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index da753730d9e2a..6e52c8ea0024d 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index 3cbf1f06f7ab2..0f608d61fe6b6 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/BaseStockStatusSelectProcessor.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/BaseStockStatusSelectProcessor.php index cbeaf2cea90e0..73aeee118e603 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/BaseStockStatusSelectProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/BaseStockStatusSelectProcessor.php @@ -49,18 +49,18 @@ public function process(Select $select) { // Does not make sense to extend query if out of stock products won't appear in tables for indexing if ($this->stockConfig->isShowOutOfStock()) { - $select->join( - ['si' => $this->resource->getTableName('cataloginventory_stock_item')], - 'si.product_id = l.product_id', + $stockIndexTableName = $this->resource->getTableName('cataloginventory_stock_status'); + $select->joinInner( + ['child_stock_default' => $stockIndexTableName], + 'child_stock_default.product_id = l.product_id', [] - ); - $select->join( - ['si_parent' => $this->resource->getTableName('cataloginventory_stock_item')], - 'si_parent.product_id = l.parent_id', + )->joinInner( + ['parent_stock_default' => $stockIndexTableName], + 'parent_stock_default.product_id = le.entity_id', [] + )->where( + 'child_stock_default.stock_status = 1 OR parent_stock_default.stock_status = 0' ); - $select->where('si.is_in_stock = ?', Stock::STOCK_IN_STOCK); - $select->orWhere('si_parent.is_in_stock = ?', Stock::STOCK_OUT_OF_STOCK); } return $select; diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index d00e5c72a4622..73810e8d4cf8b 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -9,7 +9,6 @@ use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier; -use Magento\Framework\DB\Select; use Magento\Framework\Indexer\DimensionalIndexerInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; @@ -18,7 +17,6 @@ use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; -use Magento\CatalogInventory\Model\Stock; /** * Configurable Products Price Indexer Resource model @@ -82,6 +80,11 @@ class Configurable implements DimensionalIndexerInterface */ private $baseSelectProcessor; + /** + * @var OptionsIndexerInterface + */ + private $optionsIndexer; + /** * @param BaseFinalPrice $baseFinalPrice * @param IndexTableStructureFactory $indexTableStructureFactory @@ -91,9 +94,9 @@ class Configurable implements DimensionalIndexerInterface * @param BasePriceModifier $basePriceModifier * @param bool $fullReindexAction * @param string $connectionName - * @param ScopeConfigInterface $scopeConfig + * @param ScopeConfigInterface|null $scopeConfig * @param BaseSelectProcessorInterface|null $baseSelectProcessor - * + * @param OptionsIndexerInterface|null $optionsIndexer * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -106,7 +109,8 @@ public function __construct( $fullReindexAction = false, $connectionName = 'indexer', ScopeConfigInterface $scopeConfig = null, - ?BaseSelectProcessorInterface $baseSelectProcessor = null + ?BaseSelectProcessorInterface $baseSelectProcessor = null, + ?OptionsIndexerInterface $optionsIndexer = null ) { $this->baseFinalPrice = $baseFinalPrice; $this->indexTableStructureFactory = $indexTableStructureFactory; @@ -119,6 +123,8 @@ public function __construct( $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); $this->baseSelectProcessor = $baseSelectProcessor ?: ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); + $this->optionsIndexer = $optionsIndexer + ?: ObjectManager::getInstance()->get(OptionsIndexerInterface::class); } /** @@ -175,7 +181,8 @@ private function applyConfigurableOption( true ); - $this->fillTemporaryOptionsTable($temporaryOptionsTableName, $dimensions, $entityIds); + $indexTableName = $this->getMainTable($dimensions); + $this->optionsIndexer->execute($indexTableName, $temporaryOptionsTableName, $entityIds); $this->updateTemporaryTable($temporaryPriceTable->getTableName(), $temporaryOptionsTableName); $this->getConnection()->delete($temporaryOptionsTableName); @@ -183,54 +190,6 @@ private function applyConfigurableOption( return $this; } - /** - * Put data into catalog product price indexer config option temp table - * - * @param string $temporaryOptionsTableName - * @param array $dimensions - * @param array $entityIds - * - * @return void - * @throws \Exception - */ - private function fillTemporaryOptionsTable(string $temporaryOptionsTableName, array $dimensions, array $entityIds) - { - $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - $linkField = $metadata->getLinkField(); - - $select = $this->getConnection()->select()->from( - ['i' => $this->getMainTable($dimensions)], - [] - )->join( - ['l' => $this->getTable('catalog_product_super_link')], - 'l.product_id = i.entity_id', - [] - )->join( - ['le' => $this->getTable('catalog_product_entity')], - 'le.' . $linkField . ' = l.parent_id', - [] - ); - - $this->baseSelectProcessor->process($select); - - $select->columns( - [ - 'le.entity_id', - 'customer_group_id', - 'website_id', - 'MIN(final_price)', - 'MAX(final_price)', - 'MIN(tier_price)', - ] - )->group( - ['le.entity_id', 'customer_group_id', 'website_id'] - ); - if ($entityIds !== null) { - $select->where('le.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); - } - $this->tableMaintainer->insertFromSelect($select, $temporaryOptionsTableName, []); - } - /** * Update data in the catalog product price indexer temp table * diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexer.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexer.php new file mode 100644 index 0000000000000..bceb95b00ccba --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexer.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; + +/** + * Configurable product options prices aggregator + */ +class OptionsIndexer implements OptionsIndexerInterface +{ + /** + * @var OptionsSelectBuilderInterface + */ + private $selectBuilder; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * @param OptionsSelectBuilderInterface $selectBuilder + * @param TableMaintainer $tableMaintainer + */ + public function __construct( + OptionsSelectBuilderInterface $selectBuilder, + TableMaintainer $tableMaintainer + ) { + $this->selectBuilder = $selectBuilder; + $this->tableMaintainer = $tableMaintainer; + } + + /** + * @inheritdoc + */ + public function execute(string $indexTable, string $tempIndexTable, ?array $entityIds = null): void + { + $select = $this->selectBuilder->execute($indexTable, $entityIds); + $this->tableMaintainer->insertFromSelect($select, $tempIndexTable, []); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexerInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexerInterface.php new file mode 100644 index 0000000000000..401451b2b68f7 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsIndexerInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Framework\DB\Select; + +/** + * Configurable product options prices aggregator + */ +interface OptionsIndexerInterface +{ + /** + * Aggregate configurable product options prices and save it in a temporary index table + * + * @param string $indexTable + * @param string $tempIndexTable + * @param array|null $entityIds + */ + public function execute(string $indexTable, string $tempIndexTable, ?array $entityIds = null): void; +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilder.php new file mode 100644 index 0000000000000..8bd1c2054c88b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilder.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Build select for aggregating configurable product options prices + */ +class OptionsSelectBuilder implements OptionsSelectBuilderInterface +{ + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var string + */ + private $connectionName; + + /** + * @var BaseSelectProcessorInterface + */ + private $selectProcessor; + + /** + * @param BaseSelectProcessorInterface $selectProcessor + * @param MetadataPool $metadataPool + * @param ResourceConnection $resourceConnection + * @param string $connectionName + */ + public function __construct( + BaseSelectProcessorInterface $selectProcessor, + MetadataPool $metadataPool, + ResourceConnection $resourceConnection, + string $connectionName = 'indexer' + ) { + $this->selectProcessor = $selectProcessor; + $this->metadataPool = $metadataPool; + $this->resourceConnection = $resourceConnection; + $this->connectionName = $connectionName; + } + + /** + * @inheritdoc + */ + public function execute(string $indexTable, ?array $entityIds = null): Select + { + $connection = $this->resourceConnection->getConnection($this->connectionName); + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $linkField = $metadata->getLinkField(); + + $select = $connection->select() + ->from( + ['i' => $indexTable], + [] + ) + ->join( + ['l' => $this->resourceConnection->getTableName('catalog_product_super_link', $this->connectionName)], + 'l.product_id = i.entity_id', + [] + ) + ->join( + ['le' => $this->resourceConnection->getTableName('catalog_product_entity', $this->connectionName)], + 'le.' . $linkField . ' = l.parent_id', + [] + ); + + $select->columns( + [ + 'le.entity_id', + 'customer_group_id', + 'website_id', + 'MIN(final_price)', + 'MAX(final_price)', + 'MIN(tier_price)', + ] + )->group( + ['le.entity_id', 'customer_group_id', 'website_id'] + ); + if ($entityIds !== null) { + $select->where('le.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); + } + return $this->selectProcessor->process($select); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilderInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilderInterface.php new file mode 100644 index 0000000000000..bc5084671d5b3 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/OptionsSelectBuilderInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Framework\DB\Select; + +/** + * Aggregate configurable product options prices and save it in a temporary index table + */ +interface OptionsSelectBuilderInterface +{ + /** + * Return select with aggregated configurable product options prices + * + * @param string $indexTable + * @param array|null $entityIds + * @return Select + */ + public function execute(string $indexTable, ?array $entityIds = null): Select; +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionQtyActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionQtyActionGroup.xml new file mode 100644 index 0000000000000..d9e450ffe1257 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionQtyActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductChangeOptionQtyActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="qty" type="string" defaultValue="1"/> + </arguments> + <fillField userInput="{{qty}}" selector="{{AdminProductFormConfigurationsSection.confProductQuantityCell(optionLabel)}}" stepKey="fillFieldQuantityForSecondAttributeOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionWeightActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionWeightActionGroup.xml new file mode 100644 index 0000000000000..dce6659095705 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductChangeOptionWeightActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductChangeOptionWeightActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="weight" type="string" defaultValue="1"/> + </arguments> + <fillField userInput="{{weight}}" selector="{{AdminProductFormConfigurationsSection.confProductWeightCell(optionLabel)}}" stepKey="fillFieldQuantityForSecondAttributeOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup.xml new file mode 100644 index 0000000000000..dd2e8a82f217b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup" extends="GenerateConfigurationsByAttributeCodeActionGroup"> + <annotations> + <description>EXTENDS: generateConfigurationsByAttributeCode. Skip quantity, price and images.</description> + </annotations> + <arguments> + <argument name="attributeCode" type="string" defaultValue="SomeString"/> + </arguments> + + <remove keyForRemoval="clickOnApplySingleQuantityToEachSku"/> + <remove keyForRemoval="enterAttributeQuantity"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductDisableConfigurationsActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductDisableConfigurationsActionGroup.xml index 69b2c37b6e850..c8d4438835da8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductDisableConfigurationsActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductDisableConfigurationsActionGroup.xml @@ -12,8 +12,10 @@ <arguments> <argument name="productName" type="string" defaultValue="{{SimpleProduct.name}}"/> </arguments> + <scrollTo selector="{{AdminProductFormConfigurationsSection.currentVariations}}" stepKey="scrollToVariations" /> <click selector="{{AdminProductFormConfigurationsSection.actionsBtnByProductName(productName)}}" stepKey="clickToExpandActionsSelect"/> - <click selector="{{AdminProductFormConfigurationsSection.disableProductBtn}}" stepKey="clickDisableChildProduct"/> + <click selector="{{AdminProductFormConfigurationsSection.disableProductBtnActive}}" stepKey="clickDisableChildProduct"/> <see selector="{{AdminProductFormConfigurationsSection.confProductOptionStatusCell(productName)}}" userInput="Disabled" stepKey="seeConfigDisabled"/> + <scrollTo selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" stepKey="scrollToSectionHeader" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionPriceActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionPriceActionGroup.xml new file mode 100644 index 0000000000000..83db1ac3fa954 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionPriceActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductVerifyOptionPriceActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="price" type="string" defaultValue="10"/> + </arguments> + <grabValueFrom selector="{{AdminProductFormConfigurationsSection.confProductPriceCell(optionLabel)}}" stepKey="getOptionPrice"/> + <assertEquals stepKey="assertEquals"> + <expectedResult type="string">{{price}}</expectedResult> + <actualResult type="variable">getOptionPrice</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionQtyActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionQtyActionGroup.xml new file mode 100644 index 0000000000000..78528cdaa0625 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionQtyActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductVerifyOptionQtyActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="qty" type="string" defaultValue="1"/> + </arguments> + <grabValueFrom selector="{{AdminProductFormConfigurationsSection.confProductQuantityCell(optionLabel)}}" stepKey="getOptionQty"/> + <assertEquals stepKey="assertEquals"> + <expectedResult type="string">{{qty}}</expectedResult> + <actualResult type="variable">getOptionQty</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionStatusActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionStatusActionGroup.xml new file mode 100644 index 0000000000000..a54ea8044516a --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionStatusActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductVerifyOptionStatusActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="status" type="string" defaultValue="Enabled"/> + </arguments> + <see selector="{{AdminProductFormConfigurationsSection.confProductOptionStatusCell(optionLabel)}}" userInput="{{status}}" stepKey="verifyStatus"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionWeightActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionWeightActionGroup.xml new file mode 100644 index 0000000000000..e9d528be18cf4 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductVerifyOptionWeightActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminConfigurableProductVerifyOptionWeightActionGroup"> + <arguments> + <argument name="optionLabel" type="string" defaultValue="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="weight" type="string" defaultValue="1"/> + </arguments> + <grabValueFrom selector="{{AdminProductFormConfigurationsSection.confProductWeightCell(optionLabel)}}" stepKey="getOptionWeight"/> + <assertEquals stepKey="assertEquals"> + <expectedResult type="string">{{weight}}</expectedResult> + <actualResult type="variable">getOptionWeight</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/GenerateConfigurationsByAttributeCodeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/GenerateConfigurationsByAttributeCodeActionGroup.xml index 80248cf5e00f8..b05099da8e85c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/GenerateConfigurationsByAttributeCodeActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/GenerateConfigurationsByAttributeCodeActionGroup.xml @@ -20,11 +20,11 @@ <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="{{attributeCode}}" stepKey="fillFilterAttributeCodeField"/> <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> - <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <checkOption selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> - <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <checkOption selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="99" stepKey="enterAttributeQuantity"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection/AdminProductFormConfigurationsSection.xml index 264a3d4e75032..ddb62ea9601a1 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection/AdminProductFormConfigurationsSection.xml @@ -19,6 +19,7 @@ <element name="currentVariationsAttributesCells" type="textarea" selector=".admin__control-fields[data-index='attributes']"/> <element name="currentVariationsCells" type="textarea" selector=".admin__control-fields[data-index='{{var}}']" parameterized="true"/> <element name="currentVariationsStatusCells" type="textarea" selector="._no-header[data-index='status']"/> + <element name="currentVariations" type="text" selector="[data-index=configurable-matrix]"/> <element name="currentVariationsAllRows" type="text" selector="[data-index=configurable-matrix] .data-row"/> <element name="currentVariationsProductImage" type="text" parameterized="true" selector="[data-index=configurable-matrix] .data-row:nth-of-type({{index}}) td[data-index=thumbnail_image_container] img"/> <element name="currentVariationsProductName" type="text" parameterized="true" selector="[data-index=configurable-matrix] .data-row:nth-of-type({{index}}) td[data-index=name_container]"/> @@ -34,6 +35,7 @@ <element name="addProduct" type="button" selector="//*[.='Attributes']/ancestor::tr/td[@data-index='attributes']//span[contains(text(), '{{var}}')]/ancestor::tr//a[text()='Choose a different Product']" parameterized="true"/> <element name="removeProductBtn" type="button" selector="//a[text()='Remove Product']"/> <element name="disableProductBtn" type="button" selector="//a[text()='Disable Product']"/> + <element name="disableProductBtnActive" type="button" selector="//*[@class='action-menu _active']//a[text()='Disable Product']"/> <element name="enableProductBtn" type="button" selector="//a[text()='Enable Product']"/> <element name="confProductSku" type="input" selector="//*[@name='configurable-matrix[{{arg}}][sku]']" parameterized="true"/> <element name="confProductNameCell" type="input" selector="//*[.='Attributes']/ancestor::tr//span[contains(text(), '{{var}}')]/ancestor::tr/td[@data-index='name_container']//input" parameterized="true"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckProductQtyAfterOrderCancellingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckProductQtyAfterOrderCancellingTest.xml index 0b9b5c98d9884..16ecd9b8852f6 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckProductQtyAfterOrderCancellingTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckProductQtyAfterOrderCancellingTest.xml @@ -38,7 +38,7 @@ </createData> <updateData createDataKey="createGuestCart" entity="GuestOrderPaymentMethod" stepKey="sendGuestPaymentInformation"> <requiredEntity createDataKey="createGuestCart"/> - </updateData> + </updateData> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> @@ -46,25 +46,24 @@ <after> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById"> - <argument name="orderId" value="$createGuestCart.return$"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="$createGuestCart.return$"/> </actionGroup> - <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="openOrder"/> - <actionGroup ref="AdminInvoiceWithUpdatedProductQtyActionGroup" stepKey="createPartialInvoice"> <argument name="qty" value="1"/> - </actionGroup> - - <actionGroup ref="AdminCreateShipmentFromOrderPage" stepKey="createShipment"> + </actionGroup> + + <actionGroup ref="AdminCreateShipmentFromOrderPage" stepKey="createShipment"> <argument name="Qty" value="1"/> <argument name="Number" value="111"/> - </actionGroup> - + </actionGroup> + <actionGroup ref="CancelPendingOrderActionGroup" stepKey="cancelOrder"> <argument name="orderStatus" value="Complete"/> </actionGroup> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductAddNewOptionsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductAddNewOptionsTest.xml new file mode 100644 index 0000000000000..02a3417a48ffc --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductAddNewOptionsTest.xml @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminConfigurableProductAddNewOptionsTest"> + <annotations> + <stories value="Create configurable product"/> + <title value="Adding new options to configurable product should not affect existing options"/> + <description value="Adding new options to configurable product should not affect existing options"/> + <testCaseId value="AC-1714"/> + <useCaseId value="ACP2E-101"/> + <severity value="MAJOR"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <!-- Login as Admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with three options --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption4" stepKey="createConfigProductAttributeOption4"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption5" stepKey="createConfigProductAttributeOption5"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the third option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + + <!--Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + <after> + <!-- Delete Created Data --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToEditPage"> + <argument name="productId" value="$$createConfigProduct.id$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductDisableConfigurationsActionGroup" stepKey="disableOption1"> + <argument name="productName" value="$$getConfigAttributeOption1.label$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup" stepKey="generateConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionStatusActionGroup" stepKey="checkOption1Status"> + <argument name="optionLabel" value="$$getConfigAttributeOption1.label$$"/> + <argument name="status" value="Disabled"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionStatusActionGroup" stepKey="checkOption3Status"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="status" value="Enabled"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductDisableConfigurationsActionGroup" stepKey="disableOption3"> + <argument name="productName" value="$$getConfigAttributeOption3.label$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductChangeOptionQtyActionGroup" stepKey="changeOption3Qty"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="qty" value="742"/> + </actionGroup> + + <actionGroup ref="ChangeConfigurableProductChildProductPriceActionGroup" stepKey="changeOption3Price"> + <argument name="productAttributes" value="$$getConfigAttributeOption3.label$$"/> + <argument name="productPrice" value="5"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductChangeOptionWeightActionGroup" stepKey="changeOption3Weight"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="weight" value="3"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductCreateConfigurationsAndSkipBulkByAttributeCodeActionGroup" stepKey="generateConfigurations2"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionStatusActionGroup" stepKey="checkOption1Status2"> + <argument name="optionLabel" value="$$getConfigAttributeOption1.label$$"/> + <argument name="status" value="Disabled"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionStatusActionGroup" stepKey="checkOption3Status2"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="status" value="Disabled"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionQtyActionGroup" stepKey="checkOption3Qty"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="qty" value="742"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionPriceActionGroup" stepKey="checkOption3Price"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="price" value="5"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionWeightActionGroup" stepKey="checkOption3Weight"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="weight" value="3"/> + </actionGroup> + + <actionGroup ref="GenerateConfigurationsByAttributeCodeActionGroup" stepKey="generateConfigurations3"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionQtyActionGroup" stepKey="checkOption1Qty"> + <argument name="optionLabel" value="$$getConfigAttributeOption1.label$$"/> + <argument name="qty" value="99"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionQtyActionGroup" stepKey="checkOption2Qty"> + <argument name="optionLabel" value="$$getConfigAttributeOption2.label$$"/> + <argument name="qty" value="99"/> + </actionGroup> + + <actionGroup ref="AdminConfigurableProductVerifyOptionQtyActionGroup" stepKey="checkOption3Qty3"> + <argument name="optionLabel" value="$$getConfigAttributeOption3.label$$"/> + <argument name="qty" value="99"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminConfigurableProductCreateTest.xml index a17fcf1ddf306..cac1f185bc4d9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminConfigurableProductCreateTest.xml @@ -49,6 +49,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductAddConfigurationTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductAddConfigurationTest.xml index a84fde213504e..a01da89c06eb8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductAddConfigurationTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductAddConfigurationTest.xml @@ -49,6 +49,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductRemoveConfigurationTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductRemoveConfigurationTest.xml index 7078100272eb7..aa080279ca550 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductRemoveConfigurationTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest/AdminConfigurableProductRemoveConfigurationTest.xml @@ -38,6 +38,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFiltersOnProductIndexPage"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml index 775ffe1cf1ad8..894e7987f624d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml @@ -30,6 +30,9 @@ <argument name="product" value="_defaultProduct"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters" after="deleteProduct"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute" after="clearProductsGridFilters"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml index 328984e973d5c..517261602cb1c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml @@ -42,6 +42,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clickClearFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductBasicInfoTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductBasicInfoTest.xml index 99b4cf5d4c3a5..123c509207f09 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductBasicInfoTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductBasicInfoTest.xml @@ -43,6 +43,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCanAddToCartTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCanAddToCartTest.xml index effe69a9d3b0c..6ab7d6f5500a7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCanAddToCartTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCanAddToCartTest.xml @@ -43,6 +43,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCantAddToCartTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCantAddToCartTest.xml index 8cd5b95680b55..4f5ce829929a5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCantAddToCartTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductCantAddToCartTest.xml @@ -43,6 +43,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductOptionsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductOptionsTest.xml index 1a2a1bb379df2..2b83c302d2390 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductOptionsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest/StorefrontConfigurableProductOptionsTest.xml @@ -43,6 +43,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml index 440899eb6fd4b..e4783076352da 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml @@ -55,6 +55,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductGridViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductGridViewTest.xml index 47096f317599e..c478c4cdfdde5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductGridViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductGridViewTest.xml @@ -45,6 +45,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductListViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductListViewTest.xml index e0b1e85fa9b9c..35a94439398d6 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductListViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductListViewTest.xml @@ -55,6 +55,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml index 742b67d2a6918..30c142886f9a9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml @@ -49,6 +49,9 @@ <argument name="sku" value="{{BaseConfigurableProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 715b6f46fcfce..584129dd863c8 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 01edbe6cd75ca..270e8ec746097 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -17,6 +17,8 @@ <preference for="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface" type="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProvider" /> <preference for="Magento\ConfigurableProduct\Model\AttributeOptionProviderInterface" type="Magento\ConfigurableProduct\Model\AttributeOptionProvider" /> <preference for="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface" type="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilder" /> + <preference for="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsIndexerInterface" type="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsIndexer" /> + <preference for="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilderInterface" type="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilder" /> <type name="Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option"> <plugin name="configurable_product" type="Magento\ConfigurableProduct\Model\Quote\Item\QuantityValidator\Initializer\Option\Plugin\ConfigurableProduct" sortOrder="50" /> @@ -54,7 +56,7 @@ <type name="Magento\Sales\Model\ResourceModel\Report\Bestsellers"> <arguments> <argument name="ignoredProductTypes" xsi:type="array"> - <item name="configurable" xsi:type="const">\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE</item> + <item name="configurable" xsi:type="const">Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE</item> </argument> </arguments> </type> @@ -208,6 +210,12 @@ <argument name="baseSelectProcessor" xsi:type="object">Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\BaseStockStatusSelectProcessor</argument> </arguments> </type> + <type name="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilder"> + <arguments> + <argument name="selectProcessor" xsi:type="object">Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\BaseStockStatusSelectProcessor</argument> + <argument name="connectionName" xsi:type="string">indexer</argument> + </arguments> + </type> <type name="Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Product"> <arguments> <argument name="productIndexer" xsi:type="object">Magento\Catalog\Model\Indexer\Product\Full</argument> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml index 379e129b68c7e..439fd0c934403 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml @@ -5,6 +5,7 @@ */ /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Summary */ + ?> <div data-bind="scope: '<?= /* @noEscape */ $block->getComponentName() ?>'"> <h2 class="steps-wizard-title"><?= $block->escapeHtml( @@ -52,8 +53,12 @@ "<?= /* @noEscape */ $block->getComponentName() ?>": { "component": "Magento_ConfigurableProduct/js/variations/steps/summary", "appendTo": "<?= /* @noEscape */ $block->getParentComponentName() ?>", - "variationsComponent": "<?= /* @noEscape */ $block->getData('config/form') ?>.configurableVariations", - "modalComponent": "<?= /* @noEscape */ $block->getData('config/form') ?>.configurableModal" + "variationsComponent": "<?= /* @noEscape */ $block->getData('config/form') + ?>.configurableVariations", + "modalComponent": "<?= /* @noEscape */ $block->getData('config/form') + ?>.configurableModal", + "matrixGridComponent": "<?= /* @noEscape */ $block->getData('config/form') + ?>.configurable.configurable-matrix" } } } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js index ac952ca531a34..f5c9382af0bc3 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js @@ -17,7 +17,8 @@ define([ defaults: { modules: { variationsComponent: '${ $.variationsComponent }', - modalComponent: '${ $.modalComponent }' + modalComponent: '${ $.modalComponent }', + matrixGridComponent: '${ $.matrixGridComponent }' }, notificationMessage: { text: null, @@ -96,11 +97,16 @@ define([ variationsKeys = [], gridExisting = [], gridNew = [], - gridDeleted = []; + gridDeleted = [], + matrixGridData = this.matrixGridComponent() ? + _.indexBy(this.matrixGridComponent().getUnionInsertData(), 'variationKey') : {}; this.variations = []; _.each(variations, function (options) { var product, images, sku, name, quantity, price, variation, + variationsKey = this.variationsComponent().getVariationKey(options), + productDataFromGrid = matrixGridData[variationsKey] || {}, + productDataFromWizard = {}, productId = this.variationsComponent().getProductIdByOptions(options); if (productId) { @@ -117,40 +123,61 @@ define([ }, ''); quantity = getSectionValue(this.quantityFieldName, options); - if (!quantity && productId) { - quantity = product[this.quantityFieldName]; + if (quantity) { + productDataFromWizard[this.quantityFieldName] = quantity; } price = getSectionValue('price', options); - if (!price) { - price = productId ? product.price : productPrice; + if (price) { + productDataFromWizard.price = price; } if (productId && !images.file) { images = product.images; } + productDataFromGrid = _.pick( + productDataFromGrid, + 'sku', + 'name', + 'weight', + 'status', + 'price', + 'qty' + ); + + if (productDataFromGrid.hasOwnProperty('qty')) { + productDataFromGrid[this.quantityFieldName] = productDataFromGrid.qty; + } + delete productDataFromGrid.qty; + product = _.pick( + product || {}, + 'sku', + 'name', + 'weight', + 'status', + 'price', + this.quantityFieldName + ); variation = { options: options, images: images, sku: sku, name: name, - price: price, + price: productPrice, productId: productId, weight: productWeight, editable: true }; variation[this.quantityFieldName] = quantity; + variation = _.extend(variation, product, productDataFromGrid, productDataFromWizard); if (productId) { - variation.sku = product.sku; - variation.weight = product.weight; - variation.name = product.name; gridExisting.push(this.prepareRowForGrid(variation)); } else { gridNew.push(this.prepareRowForGrid(variation)); } this.variations.push(variation); - variationsKeys.push(this.variationsComponent().getVariationKey(options)); + variationsKeys.push(variationsKey); }, this); _.each(_.omit(this.variationsComponent().productAttributesMap, variationsKeys), function (productId) { diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html index bf1e1ad5fe1e1..1e9224dc20fe3 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/template/variations/steps/summary-grid.html @@ -7,7 +7,7 @@ <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" data-bind="mageInit: {'collapsible':{'active':true,'openedState':'opened'}}"> <div class="fieldset-wrapper-title toggle" data-role="title"> - <strong class="admin__collapsible-title" data-toggle="collapse" data-bind="attr:{'data-target': '#'+id}"> + <strong class="admin__collapsible-title" data-bs-toggle="collapse" data-bind="attr:{'data-bs-target': '#'+id}"> <span data-bind="text: title"></span> </strong> </div> diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index 77c069939ac1a..6927ef82e91f6 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-catalog": "*", "magento/module-configurable-product": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/ConfigurableProductSales/composer.json b/app/code/Magento/ConfigurableProductSales/composer.json index 78b69acd010f4..69ec6b3913960 100644 --- a/app/code/Magento/ConfigurableProductSales/composer.json +++ b/app/code/Magento/ConfigurableProductSales/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-sales": "*", diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index 75de22ee779ae..4925097577eb5 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Cookie/Test/Mftf/Test/StorefrontVerifyUnsecureCookieTest.xml b/app/code/Magento/Cookie/Test/Mftf/Test/StorefrontVerifyUnsecureCookieTest.xml index 7ae6b90893165..7f7b487725dd0 100644 --- a/app/code/Magento/Cookie/Test/Mftf/Test/StorefrontVerifyUnsecureCookieTest.xml +++ b/app/code/Magento/Cookie/Test/Mftf/Test/StorefrontVerifyUnsecureCookieTest.xml @@ -28,11 +28,13 @@ <comment userInput="Adding the comment to replace CliCacheFlushActionGroup action group ('cache:flush' command) for preserving Backward Compatibility" stepKey="flushCache"/> </after> <amOnPage url="/" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> <executeJS function="return window.cookiesConfig.secure ? 'true' : 'false'" stepKey="isCookieSecure"/> <assertEquals stepKey="assertCookieIsUnsecure"> <actualResult type="variable">isCookieSecure</actualResult> <expectedResult type="string">false</expectedResult> </assertEquals> + <waitForPageLoad stepKey="waitForPageLoad2"/> <executeJS function="return jQuery.mage.cookies.defaults.secure ? 'true' : 'false'" stepKey="isCookieSecure2"/> <assertEquals stepKey="assertCookieIsSecure2"> <actualResult type="variable">isCookieSecure2</actualResult> diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 7cd98541eb880..b2d1946bc4666 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php b/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php index d10ed4979fa37..49556f75b37f0 100644 --- a/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php +++ b/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php @@ -21,12 +21,12 @@ class Sitemap extends \Magento\Framework\App\Config\Value /** * Cron string path for product alerts */ - const CRON_STRING_PATH = 'crontab/default/jobs/sitemap_generate/schedule/cron_expr'; + public const CRON_STRING_PATH = 'crontab/default/jobs/sitemap_generate/schedule/cron_expr'; /** * Cron mode path */ - const CRON_MODEL_PATH = 'crontab/default/jobs/sitemap_generate/run/model'; + public const CRON_MODEL_PATH = 'crontab/default/jobs/sitemap_generate/run/model'; /** * @var \Magento\Framework\App\Config\ValueFactory @@ -73,8 +73,12 @@ public function __construct( */ public function afterSave() { - $time = $this->getData('groups/generate/fields/time/value'); - $frequency = $this->getData('groups/generate/fields/frequency/value'); + $time = $this->getData('groups/generate/fields/time/value') ?: + explode( + ',', + $this->_config->getValue('sitemap/generate/time', $this->getScope(), $this->getScopeId()) ?: '0,0,0' + ); + $frequency = $this->getValue(); $cronExprArray = [ (int)($time[1] ?? 0), //Minute diff --git a/app/code/Magento/Cron/Model/Config/Converter/Db.php b/app/code/Magento/Cron/Model/Config/Converter/Db.php index 232ca8604774c..48b81579c6b49 100644 --- a/app/code/Magento/Cron/Model/Config/Converter/Db.php +++ b/app/code/Magento/Cron/Model/Config/Converter/Db.php @@ -29,7 +29,7 @@ public function convert($source) /** * Extract and prepare cron job data * - * @param array $jobs + * @param array $cronTab * @return array */ protected function _extractParams(array $cronTab) @@ -92,7 +92,8 @@ protected function _processScheduleParam(array $jobConfig, $jobName, array &$res protected function _processRunModel(array $jobConfig, $jobName, array &$result) { if (isset($jobConfig['run']) && is_array($jobConfig['run']) && array_key_exists('model', $jobConfig['run'])) { - $callPath = explode('::', $jobConfig['run']['model']); + $runModel = $jobConfig['run']['model']; + $callPath = $runModel ? explode('::', $runModel) : []; if (empty($callPath) || empty($callPath[0]) || empty($callPath[1])) { unset($result[$jobName]['run']); diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index 7cb4b4402a301..820fa16c4d663 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Csp/composer.json b/app/code/Magento/Csp/composer.json index 897cb39ba2760..ef3812ac1ce1f 100644 --- a/app/code/Magento/Csp/composer.json +++ b/app/code/Magento/Csp/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/CurrencySymbol/Block/Adminhtml/System/Currency/Rate/Matrix.php b/app/code/Magento/CurrencySymbol/Block/Adminhtml/System/Currency/Rate/Matrix.php index ee94195a29cc7..c0d0dd68fcc23 100644 --- a/app/code/Magento/CurrencySymbol/Block/Adminhtml/System/Currency/Rate/Matrix.php +++ b/app/code/Magento/CurrencySymbol/Block/Adminhtml/System/Currency/Rate/Matrix.php @@ -107,7 +107,7 @@ protected function _prepareRates($array) foreach ($array as $key => $rate) { foreach ($rate as $code => $value) { - $parts = explode('.', $value); + $parts = $value !== null ? explode('.', $value) : []; if (count($parts) == 2) { $parts[1] = str_pad(rtrim($parts[1], 0), 4, '0', STR_PAD_RIGHT); $array[$key][$code] = join('.', $parts); diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminCheckCurrencyConverterApiConfigurationTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminCheckCurrencyConverterApiConfigurationTest.xml index c36ce89d9dfad..91ae96e2656df 100644 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminCheckCurrencyConverterApiConfigurationTest.xml +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminCheckCurrencyConverterApiConfigurationTest.xml @@ -23,14 +23,22 @@ <!--Set currency configuration--> <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForRHD.value}}" stepKey="setAllowedCurrencyRHDAndUSD"/> <magentoCLI command="config:set {{CurrencyConverterApiKeyConfigData.path}} {{CurrencyConverterApiKeyConfigData.value}}" stepKey="setCurrencyConverterApiKey"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> <!--Create product--> <createData entity="SimpleProduct2" stepKey="createProduct"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> + <actionGroup ref="AdminOpenCurrencyRatesPageActionGroup" stepKey="gotToCurrencyRatesPage"/> + <actionGroup ref="AdminSetCurrencyRatesActionGroup" stepKey="revertCurrencyRates"> + <argument name="firstCurrency" value="USD"/> + <argument name="secondCurrency" value="EUR"/> + <argument name="rate" value="0.7067"/> + </actionGroup> <!--Set currency allow previous config--> <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setDefaultAllowedCurrencies"/> <magentoCLI command="config:set {{DefaultCurrencyConverterApiKeyConfigData.path}} {{DefaultCurrencyConverterApiKeyConfigData.value}}" stepKey="setDefaultCurrencyConverterApiKey"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> <!--Delete created data--> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> @@ -57,6 +65,7 @@ <argument name="messageType" value="warning"/> </actionGroup> <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}}" stepKey="setAllowedCurrencyEURAndUSD"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> <actionGroup ref="AdminOpenCurrencyRatesPageActionGroup" stepKey="openCurrencyRatesPageAfterSetEUR"/> <actionGroup ref="AdminImportCurrencyRatesActionGroup" stepKey="importCurrencyRatesAfterEUR"> <argument name="rateService" value="Currency Converter API"/> @@ -79,6 +88,7 @@ <see selector="{{StorefrontCategoryMainSection.productPrice}}" userInput="€" stepKey="seeEURCurrencySymbolInPrice"/> <!--Set allowed currencies greater then 10--> <magentoCLI command="config:set currency/options/allow RHD,CHW,YER,ZMK,CHE,EUR,USD,AMD,RUB,DZD,ARS,AWG" stepKey="setGreaterThanTenAllowedCurrencies"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches2"/> <!--Import rates from Currency Converter API with currencies greater then 10--> <actionGroup ref="AdminOpenCurrencyRatesPageActionGroup" stepKey="openCurrencyRatesPageAfterChangeAllowed"/> <actionGroup ref="AdminImportUnsupportedCurrencyRatesActionGroup" stepKey="importCurrencyRatesGreaterThen10"> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminDefaultCurrencySymbolsAreDisabledTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminDefaultCurrencySymbolsAreDisabledTest.xml index 997107020a574..a566dc4c37868 100644 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminDefaultCurrencySymbolsAreDisabledTest.xml +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminDefaultCurrencySymbolsAreDisabledTest.xml @@ -23,7 +23,15 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}}" stepKey="setAllowedCurrencyWebsites_EUR_USD"/> + <actionGroup ref="AdminNavigateToCurrencySetupPageActionGroup" stepKey="goToCurrencySetupPage"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchToMainWebsite"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValueForAllowedCurrency"> + <argument name="rowId" value="row_currency_options_allow"/> + </actionGroup> + <actionGroup ref="SaveStoreConfigurationActionGroup" stepKey="saveStoreConfiguration"/> + <comment userInput="BIC workaround" stepKey="setAllowedCurrencyWebsites_EUR_USD"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <actionGroup ref="AdminNavigateToCurrencySymbolsPageActionGroup" stepKey="navigateToCurrencySymbolsPage"/> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml index 146671e36af68..1b1af9acdf1a7 100644 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml @@ -43,9 +43,20 @@ <magentoCLI command="config:set {{SetDefaultCurrencyUSDConfig.path}} {{SetDefaultCurrencyUSDConfig.value}}" stepKey="setCurrencyDefaultUSD"/> <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setAllowedCurrencyUSD"/> <!--Set Currency options for Website--> + <actionGroup ref="AdminNavigateToCurrencySetupPageActionGroup" stepKey="goToCurrencySetupPage"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchToMainWebsite"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValueForDefaultDisplayCurrency"> + <argument name="rowId" value="row_currency_options_default"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValueForAllowedCurrency"> + <argument name="rowId" value="row_currency_options_allow"/> + </actionGroup> + <actionGroup ref="SaveStoreConfigurationActionGroup" stepKey="saveStoreConfiguration"/> <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseUSDWebsites"/> - <magentoCLI command="config:set --scope={{SetDefaultCurrencyUSDConfig.scope}} --scope-code={{SetDefaultCurrencyUSDConfig.scope_code}} {{SetDefaultCurrencyUSDConfig.path}} {{SetDefaultCurrencyUSDConfig.value}}" stepKey="setCurrencyDefaultUSDWebsites"/> - <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setAllowedCurrencyUSDWebsites"/> + <comment userInput="BIC workaround" stepKey="setCurrencyDefaultUSDWebsites"/> + <comment userInput="BIC workaround" stepKey="setAllowedCurrencyUSDWebsites"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!--Open created product on Storefront and place for order--> diff --git a/app/code/Magento/CurrencySymbol/composer.json b/app/code/Magento/CurrencySymbol/composer.json index f8fdb954e12a4..8dc19209cb9c8 100644 --- a/app/code/Magento/CurrencySymbol/composer.json +++ b/app/code/Magento/CurrencySymbol/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php index da20bc9b4871d..dcbe1e9b96ed9 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php @@ -23,39 +23,29 @@ class PersonalInfo extends \Magento\Backend\Block\Template * since his last activity. Used only if it's impossible to get such setting * from configuration. */ - const DEFAULT_ONLINE_MINUTES_INTERVAL = 15; + public const DEFAULT_ONLINE_MINUTES_INTERVAL = 15; /** - * Customer - * * @var \Magento\Customer\Api\Data\CustomerInterface */ protected $customer; /** - * Customer log - * * @var \Magento\Customer\Model\Log */ protected $customerLog; /** - * Customer logger - * * @var \Magento\Customer\Model\Logger */ protected $customerLogger; /** - * Customer registry - * * @var \Magento\Customer\Model\CustomerRegistry */ protected $customerRegistry; /** - * Account management - * * @var AccountManagementInterface */ protected $accountManagement; @@ -68,43 +58,31 @@ class PersonalInfo extends \Magento\Backend\Block\Template protected $groupRepository; /** - * Customer data factory - * * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory */ protected $customerDataFactory; /** - * Address helper - * * @var \Magento\Customer\Helper\Address */ protected $addressHelper; /** - * Date time - * * @var \Magento\Framework\Stdlib\DateTime */ protected $dateTime; /** - * Core registry - * * @var \Magento\Framework\Registry */ protected $coreRegistry; /** - * Address mapper - * * @var Mapper */ protected $addressMapper; /** - * Data object helper - * * @var \Magento\Framework\Api\DataObjectHelper */ protected $dataObjectHelper; @@ -439,7 +417,8 @@ public function getLastLoginDate() */ public function getStoreLastLoginDate() { - $date = strtotime($this->getCustomerLog()->getLastLoginAt()); + $lastLogin = $this->getCustomerLog()->getLastLoginAt(); + $date = $lastLogin !== null ? strtotime($lastLogin) : false; if ($date) { $date = $this->_localeDate->scopeDate($this->getCustomer()->getStoreId(), $date, true); diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index d1364dc0aeba6..bdf29d6e93b75 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -40,9 +40,9 @@ class AbstractAddress extends AbstractExtensibleModel implements AddressModelInt /** * Possible customer address types */ - const TYPE_BILLING = 'billing'; + public const TYPE_BILLING = 'billing'; - const TYPE_SHIPPING = 'shipping'; + public const TYPE_SHIPPING = 'shipping'; /** * Prefix of model events @@ -73,8 +73,6 @@ class AbstractAddress extends AbstractExtensibleModel implements AddressModelInt protected static $_regionModels = []; /** - * Directory data - * * @var \Magento\Directory\Helper\Data */ protected $_directoryData = null; @@ -241,7 +239,7 @@ public function getStreetLine($number) public function getStreetFull() { $street = $this->getData('street'); - return is_array($street) ? implode("\n", $street) : $street; + return is_array($street) ? implode("\n", $street) : ($street ?? ''); } /** diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php index f566d7e9a91f7..68234165208d2 100644 --- a/app/code/Magento/Customer/Model/FileProcessor.php +++ b/app/code/Magento/Customer/Model/FileProcessor.php @@ -27,7 +27,7 @@ class FileProcessor /** * Temporary directory name */ - const TMP_DIR = 'tmp'; + public const TMP_DIR = 'tmp'; private const CUSTOMER_FILE_URL_PATH = 'customer/index/viewfile'; @@ -215,12 +215,13 @@ public function saveTemporaryFile($fileId) ); $result = $uploader->save($path); - unset($result['path']); + if (!$result) { throw new LocalizedException( __('File can not be saved to the destination folder.') ); } + unset($result['path']); return $result; } diff --git a/app/code/Magento/Customer/Model/Indexer/Source.php b/app/code/Magento/Customer/Model/Indexer/Source.php index 89e17e01bb48e..b48e7e5ad9d05 100644 --- a/app/code/Magento/Customer/Model/Indexer/Source.php +++ b/app/code/Magento/Customer/Model/Indexer/Source.php @@ -82,6 +82,7 @@ public function addFieldToFilter($attribute, $condition = null) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function count() { return $this->customerCollection->getSize(); @@ -92,6 +93,7 @@ public function count() * * @return Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { $this->customerCollection->setPageSize($this->batchSize); diff --git a/app/code/Magento/Customer/Model/Options.php b/app/code/Magento/Customer/Model/Options.php index 4c9b9f97ad43a..ec995a12e2bc2 100644 --- a/app/code/Magento/Customer/Model/Options.php +++ b/app/code/Magento/Customer/Model/Options.php @@ -49,7 +49,7 @@ public function getNamePrefixOptions($store = null) { return $this->prepareNamePrefixSuffixOptions( $this->addressHelper->getConfig('prefix_options', $store), - $this->addressHelper->getConfig('prefix_show', $store) == NooptreqSource::VALUE_OPTIONAL + $this->addressHelper->getConfig('prefix_show', $store) === NooptreqSource::VALUE_OPTIONAL ); } @@ -63,7 +63,7 @@ public function getNameSuffixOptions($store = null) { return $this->prepareNamePrefixSuffixOptions( $this->addressHelper->getConfig('suffix_options', $store), - $this->addressHelper->getConfig('suffix_show', $store) == NooptreqSource::VALUE_OPTIONAL + $this->addressHelper->getConfig('suffix_show', $store) === NooptreqSource::VALUE_OPTIONAL ); } @@ -93,13 +93,12 @@ protected function _prepareNamePrefixSuffixOptions($options, $isOptional = false */ private function prepareNamePrefixSuffixOptions($options, $isOptional = false) { - $options = trim($options); - if (empty($options)) { + if ($options === null || empty(trim($options))) { return false; } - $result = []; - $options = explode(';', $options); + $options = explode(';', trim($options)); + foreach ($options as $value) { $result[] = $this->escaper->escapeHtml(trim($value)) ?: ' '; } diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml index d4155c2e19d2c..df5cd9c8b47a2 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml @@ -15,17 +15,17 @@ <arguments> <argument name="productName" type="string"/> </arguments> - + <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.productItem}}" stepKey="waitForElementVisible"/> <click selector="{{AdminCustomerShoppingCartProductItemSection.productItem}}" stepKey="expandProductItem"/> <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.productNameFilter}}" stepKey="waitForProductFilterFieldVisible"/> <fillField selector="{{AdminCustomerShoppingCartProductItemSection.productNameFilter}}" stepKey="setProductName" userInput="{{productName}}"/> <click selector="{{AdminCustomerShoppingCartProductItemSection.searchButton}}" stepKey="clickSearchButton"/> - <waitForAjaxLoad stepKey="waitForAjax"/> + <waitForPageLoad stepKey="waitForAjax"/> <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.firstProductCheckbox}}" stepKey="waitForElementCheckboxVisible"/> <click selector="{{AdminCustomerShoppingCartProductItemSection.firstProductCheckbox}}" stepKey="selectFirstCheckbox"/> <click selector="{{AdminCustomerShoppingCartProductItemSection.addSelectionsToMyCartButton}}" stepKey="clickAddSelectionsToMyCartButton" after="selectFirstCheckbox"/> - <waitForAjaxLoad stepKey="waitForAjax2"/> - <seeElement stepKey="seeAddedProduct" selector="{{AdminCustomerShoppingCartProductItemSection.addedProductName('productName')}}"/> + <waitForPageLoad stepKey="waitForAjax2"/> + <waitForElementVisible stepKey="seeAddedProduct" selector="{{AdminCustomerShoppingCartProductItemSection.addedProductName('productName')}}"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml index 5833bf07aeae2..df7b0e2923020 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeSingleCustomerGroupViaGridTest.xml @@ -24,6 +24,8 @@ <createData entity="Simple_US_Customer" stepKey="createCustomer"/> <createData entity="CustomerGroupChange" stepKey="createCustomerGroup"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminSystemStoreOpenPageActionGroup" stepKey="navigateToStores"/> + <actionGroup ref="AdminDeleteMultipleWebsitesActionGroup" stepKey="deleteWebsites"/> </before> <after> <!--Delete created data--> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml index 9f6d8d645e5f4..78adcd9058ec2 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml @@ -25,6 +25,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index b191ae9406610..efe18f8b631d6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -26,6 +26,10 @@ <after> <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearCustomersFilter"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{CustomerEntityOne.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml index 0cef6744d814b..29941d7223c08 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml @@ -29,6 +29,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}" /> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml index eaa3a11edb74e..7f66b657180f1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml @@ -25,6 +25,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml index fd9975c337346..8f2e20e90d758 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml @@ -28,6 +28,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml index 44eab9d0c19ae..2cd231d0bf396 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml @@ -26,6 +26,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml index 444e9aa643181..6d917d3d18b43 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml @@ -26,6 +26,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml index 5edb9d08da46d..c3dbad6708156 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml @@ -25,6 +25,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml index 87111ec6fba1a..309da1add5ffd 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml @@ -29,6 +29,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> <argument name="websiteName" value="{{secondCustomWebsite.name}}"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml index 03691eb7e7b72..059216036280a 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml @@ -34,6 +34,7 @@ </actionGroup> <seeElement selector="{{CustomersPageSection.deletedSuccessMessage}}" stepKey="seeSuccessMessage"/> <waitForPageLoad stepKey="waitForCustomerGridPageToLoad"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <!--Assert Customer is not in Grid --> <click selector="{{AdminCustomerFiltersSection.filtersButton}}" stepKey="clickFilterButton"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml index 578c6786c1505..ef4dc560d4fee 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminGridSelectAllOnPageTest.xml @@ -38,7 +38,7 @@ <click selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(($$thirdCustomer.email$$)}}" stepKey="selectThirdCustomer"/> <dontSeeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.customerCheckbox($$thirdCustomer.email$$)}}" stepKey="checkThirdCustomerCheckboxIsUnchecked"/> <!-- Click select all on page checkbox --> - <actionGroup ref="AdminSelectAllCustomers" stepKey="selectAllCustomersOnPage"/> + <actionGroup ref="AdminGridSelectAllActionGroup" stepKey="selectAllCustomersOnPage"/> <seeElement selector="{{AdminCustomerGridMainActionsSection.multicheckTick}}" stepKey="waitForElement"/> <seeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.multicheckTick}}" stepKey="checkAllSelectedCheckBoxIsChecked"/> <!-- Check all created records selected --> @@ -46,7 +46,7 @@ <seeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.customerCheckbox($$secondCustomer.email$$)}}" stepKey="checkSecondCustomerIsCheckedAfterSelectPage"/> <seeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.customerCheckbox($$thirdCustomer.email$$)}}" stepKey="checkThirdCustomerIsCheckedAfterSelectPage"/> <!-- Click deselect all on page checkbox --> - <actionGroup ref="AdminSelectAllCustomers" stepKey="deselectAllCustomersCheckbox"/> + <actionGroup ref="AdminGridDeselectAllActionGroup" stepKey="deselectAllCustomersCheckbox"/> <dontSeeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.multicheckTick}}" stepKey="checkAllSelectedCheckBoxUnchecked"/> <!-- Check all created records unselected --> <dontSeeCheckboxIsChecked selector="{{AdminCustomerGridMainActionsSection.customerCheckbox($$firstCustomer.email$$)}}" stepKey="checkFirstCustomerIsUncheckedAfterSelectPage"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminResetCustomerPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminResetCustomerPasswordTest.xml index 6ecb20ab8072a..654b9b4c9d013 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminResetCustomerPasswordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminResetCustomerPasswordTest.xml @@ -19,6 +19,9 @@ </annotations> <before> <createData entity="Simple_US_Customer" stepKey="customer"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value="customer_grid"/> + </actionGroup> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> </before> <after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml index d78d676a822d9..f0e79ee5494c8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml @@ -20,6 +20,9 @@ <before> <createData entity="Simple_Customer_Without_Address" stepKey="createCustomer"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value="customer_grid"/> + </actionGroup> <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> </before> <after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartVerifyThatErrorMessageShouldNotDisappearTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartVerifyThatErrorMessageShouldNotDisappearTest.xml new file mode 100644 index 0000000000000..ccca330f5ff1a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartVerifyThatErrorMessageShouldNotDisappearTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddProductToCartVerifyThatErrorMessageShouldNotDisappearTest"> + <annotations> + <title value="Adding a product to cart from product detail page with higher quantity then available when synchronize widget products with backend storage enabled"/> + <description value="Adding a product to cart from product detail page with higher quantity then available when synchronize widget products with backend storage enabled"/> + <features value="Module/ Catalog"/> + <severity value="AVERAGE"/> + <testCaseId value="AC-1571"/> + <useCaseId value="ACP2E-23"/> + <stories value="[Magento Cloud] Error message in PDP disappearing quickly"/> + <group value="customer"/> + </annotations> + + <before> + <!-- Set in Stores > Configuration > Catalog > Catalog > Recently Viewed/Compared Products > Synchronize Widget Products With Backend Storage = "Yes" --> + <magentoCLI command="config:set {{EnableSynchronizeWidgetProductsWithBackendStorage.path}} {{EnableSynchronizeWidgetProductsWithBackendStorage.value}}" stepKey="setEnableSynchronizeWidgetProductsWithBackendStorage"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!--Reindex and flush cache--> + <magentoCLI command="cron:run --group=index" stepKey="runCronReindex"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <magentoCLI command="config:set {{DisableSynchronizeWidgetProductsWithBackendStorage.path}} {{DisableSynchronizeWidgetProductsWithBackendStorage.value}}" stepKey="setDisableSynchronizeWidgetProductsWithBackendStorage"/> + <!--Reindex and flush cache--> + <magentoCLI command="cron:run --group=index" stepKey="runCronReindex"/> + </after> + + <waitForPageLoad time="60" stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPage"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <fillField selector="{{StorefrontProductInfoMainSection.qty}}" userInput="1001" stepKey="fillQuantity"/> + + <actionGroup ref="StorefrontProductPageAddSimpleProductToCartActionGroup" stepKey="addProductToCart"/> + <!-- Check that error remains --> + <actionGroup ref="StorefrontAssertProductAddToCartErrorMessageActionGroup" stepKey="assertFailure"> + <argument name="message" value="The requested qty is not available"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml index b9c558c65c513..82573f1a986f0 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml @@ -140,6 +140,11 @@ <argument name="taxClassName" value="UK_zero"/> </actionGroup> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{CustomerEntityOne.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> + <!--Log Out--> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerWithDateOfBirthTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerWithDateOfBirthTest.xml index 148afb6d67c21..d9e665ec7a2ad 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerWithDateOfBirthTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerWithDateOfBirthTest.xml @@ -41,5 +41,6 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressBelgiumTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressBelgiumTest.xml index b800b7870b5c5..d92b47c3990a1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressBelgiumTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressBelgiumTest.xml @@ -34,6 +34,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressChinaTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressChinaTest.xml index 41e0a5d1b7793..afc9f1a88654b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressChinaTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressChinaTest.xml @@ -34,6 +34,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml index 1ecfb20b38e19..1f92b429603e6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml @@ -35,6 +35,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml index 8e29452b5495e..7b5ad9d70fd7c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml @@ -35,6 +35,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml index eda4d189eda64..6f2ed3ac11805 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml @@ -39,6 +39,7 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteNewUser"> <argument name="email" value="{{Colorado_US_Customer.email}}"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php index 16caf346c808c..edc15bc60b4dd 100644 --- a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php +++ b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php @@ -81,6 +81,7 @@ public function __construct( * * @return array */ + #[\ReturnTypeWillChange] public function jsonSerialize() { if ($this->options === null) { diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 767b24d2ce388..ccf38f4fd5d7b 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json index c3ba4aed610f9..1073377600578 100644 --- a/app/code/Magento/CustomerAnalytics/composer.json +++ b/app/code/Magento/CustomerAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-customer-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-customer": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json index 55fd9d7638f04..20381eb7e065e 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/composer.json +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-downloadable-graph-ql": "*", "magento/module-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/CustomerGraphQl/composer.json b/app/code/Magento/CustomerGraphQl/composer.json index 28aaa1942cc7d..2762dbe72cf47 100644 --- a/app/code/Magento/CustomerGraphQl/composer.json +++ b/app/code/Magento/CustomerGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-authorization": "*", "magento/module-customer": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/CustomerImportExport/composer.json b/app/code/Magento/CustomerImportExport/composer.json index 0583e96359bcf..70650f06b4e0d 100644 --- a/app/code/Magento/CustomerImportExport/composer.json +++ b/app/code/Magento/CustomerImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index c8bc5e694af5e..0c6b5895c1b4f 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-require-js": "*", diff --git a/app/code/Magento/Developer/composer.json b/app/code/Magento/Developer/composer.json index dedf18d1d808f..5144438cd9d4d 100644 --- a/app/code/Magento/Developer/composer.json +++ b/app/code/Magento/Developer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-store": "*" diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index d9540c3041ce6..7ea0e670d0d16 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -482,11 +482,12 @@ public function setRequest(\Magento\Framework\DataObject $request) ); $shippingWeight = $request->getPackageWeight(); + $destStreet = $request->getDestStreet() !== null ? str_replace("\n", '', $request->getDestStreet()) : ''; $requestObject->setValue(sprintf('%.2f', $request->getPackageValue())) ->setValueWithDiscount($request->getPackageValueWithDiscount()) ->setCustomsValue($request->getPackageCustomsValue()) - ->setDestStreet($this->string->substr(str_replace("\n", '', $request->getDestStreet()), 0, 35)) + ->setDestStreet($this->string->substr($destStreet, 0, 35)) ->setDestStreetLine2($request->getDestStreetLine2()) ->setDestCity($request->getDestCity()) ->setOrigCompanyName($request->getOrigCompanyName()) @@ -939,7 +940,7 @@ protected function _getDimension($dimension, $configWeightUnit = false) ); } - return round($dimension, 3); + return round((float) $dimension, 3); } /** diff --git a/app/code/Magento/Dhl/composer.json b/app/code/Magento/Dhl/composer.json index d16d5d3ae0401..75a43b0cdad81 100644 --- a/app/code/Magento/Dhl/composer.json +++ b/app/code/Magento/Dhl/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Directory/Test/Mftf/ActionGroup/AdminNavigateToCurrencySetupPageActionGroup.xml b/app/code/Magento/Directory/Test/Mftf/ActionGroup/AdminNavigateToCurrencySetupPageActionGroup.xml new file mode 100644 index 0000000000000..0f0237ee8ec33 --- /dev/null +++ b/app/code/Magento/Directory/Test/Mftf/ActionGroup/AdminNavigateToCurrencySetupPageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToCurrencySetupPageActionGroup"> + <annotations> + <description>Go to the currency setup admin configuration page.</description> + </annotations> + <amOnPage url="{{AdminCurrencySetupPage.url}}" stepKey="goToCurrencySetupPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Directory/composer.json b/app/code/Magento/Directory/composer.json index 6491dd52eee67..058ec128000a4 100644 --- a/app/code/Magento/Directory/composer.json +++ b/app/code/Magento/Directory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/DirectoryGraphQl/composer.json b/app/code/Magento/DirectoryGraphQl/composer.json index 29b0a76122c11..be5f0c19de967 100644 --- a/app/code/Magento/DirectoryGraphQl/composer.json +++ b/app/code/Magento/DirectoryGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-directory": "*", "magento/module-store": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/Downloadable/Helper/File.php b/app/code/Magento/Downloadable/Helper/File.php index 10ee9ff405ec8..f20e1999f4c16 100644 --- a/app/code/Magento/Downloadable/Helper/File.php +++ b/app/code/Magento/Downloadable/Helper/File.php @@ -5,7 +5,10 @@ */ namespace Magento\Downloadable\Helper; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +use Magento\MediaStorage\Model\File\Uploader; /** * Downloadable Products File Helper @@ -16,8 +19,6 @@ class File extends \Magento\Framework\App\Helper\AbstractHelper { /** - * Core file storage database - * * @var \Magento\MediaStorage\Helper\File\Storage\Database */ protected $_coreFileStorageDatabase = null; @@ -41,6 +42,7 @@ class File extends \Magento\Framework\App\Helper\AbstractHelper * @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase * @param \Magento\Framework\Filesystem $filesystem * @param array $mimeTypes + * @throws FileSystemException */ public function __construct( \Magento\Framework\App\Helper\Context $context, @@ -61,28 +63,34 @@ public function __construct( /** * Upload file from temporary folder. + * * @param string $tmpPath - * @param \Magento\MediaStorage\Model\File\Uploader $uploader + * @param Uploader $uploader + * * @return array */ - public function uploadFromTmp($tmpPath, \Magento\MediaStorage\Model\File\Uploader $uploader) + public function uploadFromTmp($tmpPath, Uploader $uploader) { $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(true); $absoluteTmpPath = $this->_mediaDirectory->getAbsolutePath($tmpPath); $result = $uploader->save($absoluteTmpPath); - unset($result['path']); + if (is_array($result)) { + unset($result['path']); + } return $result; } /** * Checking file for moving and move it + * * @param string $baseTmpPath * @param string $basePath * @param string $file + * * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function moveFileFromTmp($baseTmpPath, $basePath, $file) { @@ -92,7 +100,7 @@ public function moveFileFromTmp($baseTmpPath, $basePath, $file) try { $fileName = $this->_moveFileFromTmp($baseTmpPath, $basePath, $file[0]['file']); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Something went wrong while saving the file(s).') ); } @@ -104,6 +112,7 @@ public function moveFileFromTmp($baseTmpPath, $basePath, $file) /** * Check if file exist in filesystem and try to re-create it from database record if negative. + * * @param string $file * @return bool|int */ @@ -130,12 +139,9 @@ protected function _moveFileFromTmp($baseTmpPath, $basePath, $file) if (strrpos($file, '.tmp') == strlen($file) - 4) { $file = substr($file, 0, strlen($file) - 4); } - - $destFile = dirname( - $file - ) . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( - $this->getFilePath($basePath, $file) - ); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $destFile = dirname($file) . '/' + . Uploader::getNewFileName($this->getFilePath($basePath, $file)); $this->_coreFileStorageDatabase->copyFile( $this->getFilePath($baseTmpPath, $file), @@ -180,6 +186,7 @@ public function getFileFromPathFile($pathFile) /** * Get filesize in bytes. + * * @param string $file * @return int */ @@ -189,6 +196,8 @@ public function getFileSize($file) } /** + * Get file type + * * @param string $filePath * @return string */ @@ -199,6 +208,8 @@ public function getFileType($filePath) } /** + * Get file type by ext + * * @param string $ext * @return string */ @@ -212,6 +223,8 @@ protected function _getFileTypeByExt($ext) } /** + * Get all file types + * * @return array */ public function getAllFileTypes() @@ -220,6 +233,8 @@ public function getAllFileTypes() } /** + * Get all mine types + * * @return array */ public function getAllMineTypes() diff --git a/app/code/Magento/Downloadable/Model/File/ContentUploader.php b/app/code/Magento/Downloadable/Model/File/ContentUploader.php index ab226b40c3cb6..01e721dbb66a4 100644 --- a/app/code/Magento/Downloadable/Model/File/ContentUploader.php +++ b/app/code/Magento/Downloadable/Model/File/ContentUploader.php @@ -18,9 +18,9 @@ class ContentUploader extends Uploader implements \Magento\Downloadable\Api\Data\File\ContentUploaderInterface { /** - * Default MIME type + * Default MIME type for header "application/octet-stream" */ - const DEFAULT_MIME_TYPE = 'application/octet-stream'; + public const DEFAULT_MIME_TYPE = 'application/octet-stream'; /** * Filename prefix for temporary files @@ -79,6 +79,7 @@ public function __construct( * * @param ContentInterface $fileContent * @return array + * @phpcs:disable Magento2.Functions.DiscouragedFunction */ protected function decodeContent(ContentInterface $fileContent) { @@ -105,7 +106,8 @@ protected function getTmpFileName() } /** - * {@inheritdoc} + * @inheritdoc + * @phpcs:disable Magento2.Functions.DiscouragedFunction */ public function upload(ContentInterface $fileContent, $contentType) { @@ -118,9 +120,13 @@ public function upload(ContentInterface $fileContent, $contentType) $this->setAllowRenameFiles(true); $this->setFilesDispersion(true); $result = $this->save($this->getDestinationDirectory($contentType)); - unset($result['path']); - $result['status'] = 'new'; - $result['name'] = substr($result['file'], strrpos($result['file'], '/') + 1); + + if ($result) { + unset($result['path']); + $result['status'] = 'new'; + $result['name'] = substr($result['file'], strrpos($result['file'], '/') + 1); + } + return $result; } diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AddDownloadableProductLinkWithMaxDownloadsActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AddDownloadableProductLinkWithMaxDownloadsActionGroup.xml index fb21724130042..29266eeb41671 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AddDownloadableProductLinkWithMaxDownloadsActionGroup.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AddDownloadableProductLinkWithMaxDownloadsActionGroup.xml @@ -25,6 +25,7 @@ <selectOption userInput="{{link.shareable}}" selector="{{AdminProductDownloadableSection.addLinkShareableSelector('0')}}" stepKey="selectDownloadableLinkShareable"/> <fillField userInput="{{link.max_downloads}}" selector="{{AdminProductDownloadableSection.addLinkMaxDownloadsInput('0')}}" stepKey="fillDownloadableLinkMaxDownloads"/> <attachFile userInput="{{link.file}}" selector="{{AdminProductDownloadableSection.addLinkFileUploadFile('0')}}" stepKey="fillDownloadableLinkUploadFile"/> + <waitForPageLoad stepKey="waitForFileLoad"/> <fillField userInput="{{link.sample}}" selector="{{AdminProductDownloadableSection.addLinkSampleUrlInput('0')}}" stepKey="fillDownloadableLinkSampleUrl"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml index 3a1eb63a96ae1..ff442e9089bb1 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -73,23 +73,27 @@ <actionGroup ref="StorefrontGuestCheckoutProceedToPaymentStepActionGroup" stepKey="clickNext"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForShipmentPageLoad"/> <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> - <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="waitForPaymentSectionLoaded"/> + <comment userInput="BIC workaround" stepKey="clickPlaceOrderButton"/> + <comment userInput="BIC workaround" stepKey="orderIsSuccessfullyPlaced"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> - <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchOrder"> - <argument name="keyword" value="$grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="onOrdersPage"/> + <comment userInput="BIC workaround" stepKey="searchOrder"/> + <comment userInput="BIC workaround" stepKey="clickOrderRow"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="goToOrderAdmin"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> <actionGroup ref="AdminCreateInvoiceActionGroup" stepKey="createCreditMemo"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <actionGroup ref="AdminOpenAndFillCreditMemoRefundActionGroup" stepKey="fillCreditMemoRefund"> diff --git a/app/code/Magento/Downloadable/Test/Unit/Helper/FileTest.php b/app/code/Magento/Downloadable/Test/Unit/Helper/FileTest.php index 11a4422707ed8..5ade044b24298 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Helper/FileTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Helper/FileTest.php @@ -25,15 +25,11 @@ class FileTest extends TestCase private $file; /** - * Core file storage database - * * @var Database|MockObject */ private $coreFileStorageDatabase; /** - * Filesystem object. - * * @var \Magento\Framework\Filesystem|MockObject */ private $filesystem; @@ -50,6 +46,14 @@ class FileTest extends TestCase */ private $appContext; + /** + * @var Uploader|MockObject + */ + private $uploader; + + /** + * @inheritDoc + */ protected function setUp(): void { $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class) @@ -91,21 +95,49 @@ protected function setUp(): void $this->coreFileStorageDatabase, $this->filesystem ); + + $this->uploader = $this->getMockBuilder(Uploader::class) + ->disableOriginalConstructor() + ->getMock(); } + /** + * @return void + */ public function testUploadFromTmp() { - $uploaderMock = $this->getMockBuilder(Uploader::class) - ->disableOriginalConstructor() - ->getMock(); - $uploaderMock->expects($this->once())->method('setAllowRenameFiles'); - $uploaderMock->expects($this->once())->method('setFilesDispersion'); + $this->uploader->expects($this->once())->method('setAllowRenameFiles'); + $this->uploader->expects($this->once())->method('setFilesDispersion'); $this->mediaDirectory->expects($this->once())->method('getAbsolutePath')->willReturn('absPath'); - $uploaderMock->expects($this->once())->method('save')->with('absPath') + $this->uploader->expects($this->once())->method('save')->with('absPath') ->willReturn(['file' => 'file.jpg', 'path' => 'absPath']); - $result = $this->file->uploadFromTmp('tmpPath', $uploaderMock); + $result = $this->file->uploadFromTmp('tmpPath', $this->uploader); $this->assertArrayNotHasKey('path', $result); } + + /** + * @return void + */ + public function testUploadFromTmpSuccess(): void + { + $tmpPath = __DIR__; + $data = ['path' => __DIR__ . DIRECTORY_SEPARATOR . 'media', 'file' => 'test_image.jpg']; + $this->uploader->method('save')->willReturn($data); + $result = $this->file->uploadFromTmp($tmpPath, $this->uploader); + $this->assertEquals('test_image.jpg', $result['file']); + $this->assertEquals(1, count($result)); + } + + /** + * @return void + */ + public function testUploadFromTmpFail(): void + { + $tmpPath = __DIR__; + $this->uploader->expects($this->once())->method('save')->willReturn(false); + $result = $this->file->uploadFromTmp($tmpPath, $this->uploader); + $this->assertEquals(false, $result); + } } diff --git a/app/code/Magento/Downloadable/composer.json b/app/code/Magento/Downloadable/composer.json index 866b842f2cfcf..7b668ab581877 100644 --- a/app/code/Magento/Downloadable/composer.json +++ b/app/code/Magento/Downloadable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/DownloadableGraphQl/composer.json b/app/code/Magento/DownloadableGraphQl/composer.json index 5f64b59705436..106998e451354 100644 --- a/app/code/Magento/DownloadableGraphQl/composer.json +++ b/app/code/Magento/DownloadableGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-downloadable": "*", diff --git a/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithFileLinksTest.xml b/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithFileLinksTest.xml index 441395e3114a9..8ecff6a1d0c5d 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithFileLinksTest.xml +++ b/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithFileLinksTest.xml @@ -201,17 +201,19 @@ <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrder"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlacePurchaseOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Create Invoice --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="goToOrderInAdmin"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="goToOrderInAdmin"> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> <!-- Storefront: Go to Purchased Downloadable Product & Verify Link --> <actionGroup ref="StorefrontNavigateToCustomerDownloadableProductsPageActionGroup" stepKey="goToCustomerDownloadableProductsPage"/> - <see userInput="{{ImportProduct_Downloadable_FileLinks.linkTitle}}" selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="seeDownloadableLink2"/> + <waitForText userInput="{{ImportProduct_Downloadable_FileLinks.linkTitle}}" selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="seeDownloadableLink2"/> <click selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="clickDownloadableLink"/> <switchToNextTab stepKey="switchToDownloadedLinkTab3"/> <waitForElement selector="{{StorefrontDownloadableLinkSection.downloadableLinkImage}}" stepKey="seeImage3"/> diff --git a/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithUrlLinksTest.xml b/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithUrlLinksTest.xml index 22cfca5224ad6..9fa254b877597 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithUrlLinksTest.xml +++ b/app/code/Magento/DownloadableImportExport/Test/Mftf/Test/AdminImportDownloadableProductsWithUrlLinksTest.xml @@ -211,17 +211,19 @@ <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrder"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlacePurchaseOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Create Invoice --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="goToOrderInAdmin"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="goToOrderInAdmin"> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> <!-- Storefront: Go to Purchased Downloadable Product & Verify Link --> <actionGroup ref="StorefrontNavigateToCustomerDownloadableProductsPageActionGroup" stepKey="goToCustomerDownloadableProductsPage"/> - <see userInput="{{ImportProduct_Downloadable_UrlLinks.linkTitle}}" selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="seeDownloadableLink2"/> + <waitForText userInput="{{ImportProduct_Downloadable_UrlLinks.linkTitle}}" selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="seeDownloadableLink2"/> <click selector="{{StorefrontCustomerDownloadableProductsSection.downloadableLinkByOrderNumber({$grabOrderNumber})}}" stepKey="clickDownloadableLink"/> <switchToNextTab stepKey="switchToDownloadedLinkTab3"/> <waitForElement selector="{{StorefrontDownloadableLinkSection.downloadableLinkTitle(ImportProduct_Downloadable_UrlLinks.linkFileName)}}" stepKey="seeImageTitle3"/> diff --git a/app/code/Magento/DownloadableImportExport/composer.json b/app/code/Magento/DownloadableImportExport/composer.json index 0a639ae1938ab..d8aff6e6db76b 100644 --- a/app/code/Magento/DownloadableImportExport/composer.json +++ b/app/code/Magento/DownloadableImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/Eav/composer.json b/app/code/Magento/Eav/composer.json index b4598326cb3a7..5206cc2e3bf8c 100644 --- a/app/code/Magento/Eav/composer.json +++ b/app/code/Magento/Eav/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/EavGraphQl/composer.json b/app/code/Magento/EavGraphQl/composer.json index 6de95de2ae518..59381c9173517 100644 --- a/app/code/Magento/EavGraphQl/composer.json +++ b/app/code/Magento/EavGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-eav": "*" }, diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json index c0429b2dbc94c..0f117609525dd 100644 --- a/app/code/Magento/Elasticsearch/composer.json +++ b/app/code/Magento/Elasticsearch/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-elasticsearch", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-advanced-search": "*", "magento/module-catalog": "*", "magento/module-catalog-search": "*", @@ -12,7 +12,7 @@ "magento/module-store": "*", "magento/module-catalog-inventory": "*", "magento/framework": "*", - "elasticsearch/elasticsearch": "~7.15.0" + "elasticsearch/elasticsearch": "~7.16.0" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json index da969f0173660..4083140e13b96 100644 --- a/app/code/Magento/Elasticsearch6/composer.json +++ b/app/code/Magento/Elasticsearch6/composer.json @@ -2,13 +2,13 @@ "name": "magento/module-elasticsearch-6", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-advanced-search": "*", "magento/module-catalog-search": "*", "magento/module-search": "*", "magento/module-elasticsearch": "*", - "elasticsearch/elasticsearch": "~7.15.0" + "elasticsearch/elasticsearch": "~7.16.0" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Elasticsearch7/composer.json b/app/code/Magento/Elasticsearch7/composer.json index ee60b7afb0d18..a41a62d2fa5eb 100644 --- a/app/code/Magento/Elasticsearch7/composer.json +++ b/app/code/Magento/Elasticsearch7/composer.json @@ -2,10 +2,10 @@ "name": "magento/module-elasticsearch-7", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-elasticsearch": "*", - "elasticsearch/elasticsearch": "~7.15.0", + "elasticsearch/elasticsearch": "~7.16.0", "magento/module-advanced-search": "*", "magento/module-catalog-search": "*" }, diff --git a/app/code/Magento/Email/composer.json b/app/code/Magento/Email/composer.json index bae17e5f6c77b..36c361aa33fea 100644 --- a/app/code/Magento/Email/composer.json +++ b/app/code/Magento/Email/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/Email/view/adminhtml/web/js/variables.js b/app/code/Magento/Email/view/adminhtml/web/js/variables.js index 7a671823ace02..a0bfe64795273 100644 --- a/app/code/Magento/Email/view/adminhtml/web/js/variables.js +++ b/app/code/Magento/Email/view/adminhtml/web/js/variables.js @@ -133,7 +133,7 @@ define([ } textareaElm.focus(); textareaElm.scrollTop = scrollPos; - jQuery(textareaElm).change(); + jQuery(textareaElm).trigger('change'); textareaElm = null; } } diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json index bb04bde39861c..828f5d08ac977 100644 --- a/app/code/Magento/EncryptionKey/composer.json +++ b/app/code/Magento/EncryptionKey/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*" diff --git a/app/code/Magento/Fedex/composer.json b/app/code/Magento/Fedex/composer.json index a98527760e250..aeb3276a03297 100644 --- a/app/code/Magento/Fedex/composer.json +++ b/app/code/Magento/Fedex/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GiftMessage/Block/Message/Inline.php b/app/code/Magento/GiftMessage/Block/Message/Inline.php index 475f1c2b717ae..f1993126200fb 100644 --- a/app/code/Magento/GiftMessage/Block/Message/Inline.php +++ b/app/code/Magento/GiftMessage/Block/Message/Inline.php @@ -36,8 +36,6 @@ class Inline extends \Magento\Framework\View\Element\Template protected $_template = 'Magento_GiftMessage::inline.phtml'; /** - * Gift message message - * * @var \Magento\GiftMessage\Helper\Message|null */ protected $_giftMessageMessage = null; @@ -324,7 +322,8 @@ public function getEntityHasMessage() */ public function getEscaped($value, $defaultValue = '') { - return $this->escapeHtml(trim($value) != '' ? $value : $defaultValue); + $value = ($value !== null && trim($value) != '') ? $value : $defaultValue; + return $this->escapeHtml($value); } /** diff --git a/app/code/Magento/GiftMessage/Model/GiftMessageManager.php b/app/code/Magento/GiftMessage/Model/GiftMessageManager.php index 605fd221462d9..dbb285cc0b489 100644 --- a/app/code/Magento/GiftMessage/Model/GiftMessageManager.php +++ b/app/code/Magento/GiftMessage/Model/GiftMessageManager.php @@ -25,10 +25,13 @@ public function __construct( } /** + * Save gift message information. + * * @param array $giftMessages * @param \Magento\Quote\Model\Quote $quote * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function add($giftMessages, $quote) { @@ -61,11 +64,12 @@ public function add($giftMessages, $quote) $giftMessage->load($entity->getGiftMessageId()); } - if (trim($message['message']) == '') { + if ($message['message'] === null || trim($message['message']) == '') { if ($giftMessage->getId()) { try { $giftMessage->delete(); $entity->setGiftMessageId(0)->save(); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch } catch (\Exception $e) { } } @@ -84,6 +88,7 @@ public function add($giftMessages, $quote) )->save(); $entity->setGiftMessageId($giftMessage->getId())->save(); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch } catch (\Exception $e) { } } diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index bfa3f4fe06437..3b29e9cff2b91 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml b/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml index f89657d3c5d90..64ed9a30108c1 100644 --- a/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml +++ b/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml @@ -4,7 +4,10 @@ * See COPYING.txt for license details. */ +// phpcs:disable Magento2.Files.LineLength, Generic.Files.LineLength + /** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ +/** @var Magento\Framework\Escaper $escaper */ ?> <?php $_giftMessage = false; switch ($block->getCheckoutType()): @@ -61,9 +64,7 @@ switch ($block->getCheckoutType()): name="giftmessage[quote][<?= (int) $block->getEntity()->getId() ?>][from]" id="gift-message-whole-from" title="<?= $block->escapeHtmlAttr(__('From')) ?>" - value="<?= /* @noEscape */ $block - ->getEscaped($block->getMessage()->getSender(), $block->getDefaultFrom()) - ?>" + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage()->getSender(), $block->getDefaultFrom()) ?>" class="input-text"> </div> </div> @@ -75,9 +76,8 @@ switch ($block->getCheckoutType()): <input type="text" name="giftmessage[quote][<?= (int) $block->getEntity()->getId() ?>][to]" id="gift-message-whole-to" title="<?= $block->escapeHtmlAttr(__('To')) ?>" - value="<?= /* @noEscape */ $block - ->getEscaped($block->getMessage()->getRecipient(), $block->getDefaultTo()) - ?>" class="input-text"> + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage()->getRecipient(), $block->getDefaultTo()) ?>" + class="input-text"> </div> </div> <div class="field text"> @@ -87,7 +87,7 @@ switch ($block->getCheckoutType()): <div class="control"> <textarea id="gift-message-whole-message" class="input-text" name="giftmessage[quote][<?=(int)$block->getEntity()->getId()?>][message]" - title="<?= $block->escapeHtmlAttr(__('Message')) ?>" rows="5" cols="10"><?= /* @noEscape */ $block->getEscaped($block->getMessage()->getMessage()) ?></textarea> + title="<?= $block->escapeHtmlAttr(__('Message')) ?>" rows="5" cols="10"><?= $escaper->escapeHtml($block->getMessage()->getMessage()) ?></textarea> </div> </div> </fieldset> @@ -179,8 +179,7 @@ script; name="giftmessage[quote_item][<?= (int) $_item->getId() ?>][to]" id="gift-message-<?= (int) $_item->getId() ?>-to" title="<?= $block->escapeHtmlAttr(__('To')) ?>" - value="<?= /* @noEscape */ $block->getEscaped($block - ->getMessage($_item)->getRecipient(), $block->getDefaultTo()) ?>" + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage($_item)->getRecipient(), $block->getDefaultTo()) ?>" class="input-text"> </div> </div> @@ -194,7 +193,7 @@ script; name="giftmessage[quote_item][<?= (int) $_item->getId() ?>][message]" title="<?= $block->escapeHtmlAttr(__('Message')) ?>" - rows="5" cols="40"><?= /* @noEscape */ $block->getEscaped($block->getMessage($_item)->getMessage()) ?></textarea> + rows="5" cols="40"><?= $escaper->escapeHtml($block->getMessage($_item)->getMessage()) ?></textarea> </div> </div> </fieldset> @@ -221,6 +220,7 @@ script; </dt> </dl> </fieldset> + <?php // phpcs:ignore Magento2.Legacy.PhtmlTemplate ?> <script type="text/x-magento-init"> { "#allow_gift_options, #allow_gift_options_for_order, #allow_gift_options_for_items": { @@ -291,8 +291,8 @@ script; ->getId() ?>][from]" id="gift-message-<?= (int) $block->getEntity()->getId() ?>-from" title="<?= $block->escapeHtmlAttr(__('From')) ?>" - value="<?= /* @noEscape */ $block->getEscaped($block->getMessage() - ->getSender(), $block->getDefaultFrom()) ?>" class="input-text"> + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage()->getSender(), $block->getDefaultFrom()) ?>" + class="input-text"> </div> </div> <div class="field to"> @@ -304,8 +304,8 @@ script; ->getId() ?>][to]" id="gift-message-<?= (int) $block->getEntity()->getId() ?>-to" title="<?= $block->escapeHtmlAttr(__('To')) ?>" - value="<?= /* @noEscape */ $block->getEscaped($block->getMessage() - ->getRecipient(), $block->getDefaultTo()) ?>" class="input-text"> + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage()->getRecipient(), $block->getDefaultTo()) ?>" + class="input-text"> </div> </div> <div class="field text"> @@ -316,7 +316,7 @@ script; <textarea id="gift-message-<?= (int) $block->getEntity()->getId() ?>-message" class="input-text" name="giftmessage[quote_address][<?= (int) $block ->getEntity()->getId() ?>][message]" - title="<?= $block->escapeHtmlAttr(__('Message')) ?>" rows="5" cols="40"><?= /* @noEscape */ $block->getEscaped($block->getMessage()->getMessage()) ?></textarea> + title="<?= $block->escapeHtmlAttr(__('Message')) ?>" rows="5" cols="40"><?= $escaper->escapeHtml($block->getMessage()->getMessage()) ?></textarea> </div> </div> </fieldset> @@ -391,9 +391,8 @@ script; name="giftmessage[quote_address_item][<?= (int) $_item->getId() ?>][from]" id="gift-message-<?= (int) $_item->getId() ?>-from" title="<?= $block->escapeHtmlAttr(__('From')) ?>" - value="<?= /* @noEscape */ $block->getEscaped($block - ->getMessage($_item)->getSender(), $block->getDefaultFrom()) - ?>" class="input-text"> + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage($_item)->getSender(), $block->getDefaultFrom()) ?>" + class="input-text"> </div> </div> <div class="field to"> @@ -405,10 +404,8 @@ script; name="giftmessage[quote_address_item][<?= (int) $_item->getId() ?>][to]" id="gift-message-<?= (int) $_item->getId() ?>-to" title="<?= $block->escapeHtmlAttr(__('To')) ?>" - value= - "<?= /* @noEscape */ $block->getEscaped($block - ->getMessage($_item)->getRecipient(), $block->getDefaultTo()) - ?>" class="input-text"> + value="<?= /* @noEscape */ $block->getEscaped($block->getMessage($_item)->getRecipient(), $block->getDefaultTo()) ?>" + class="input-text"> </div> </div> <div class="field text"> @@ -421,7 +418,7 @@ script; name="giftmessage[quote_address_item][<?= (int) $_item ->getId() ?>][message]" title="<?= $block->escapeHtmlAttr(__('Message')) ?>" rows="5" - cols="10"><?= /* @noEscape */ $block->getEscaped($block->getMessage($_item)->getMessage()) ?></textarea> + cols="10"><?= $escaper->escapeHtml($block->getMessage($_item)->getMessage()) ?></textarea> </div> </div> </fieldset> @@ -437,7 +434,8 @@ script; </dt> </dl> </fieldset> - <?php $entityId = (int) $block->getEntity()->getId(); ?> + <?php $entityId = (int) $block->getEntity()->getId(); ?> + <?php // phpcs:ignore Magento2.Legacy.PhtmlTemplate ?> <script type="text/x-magento-init"> { "#allow_gift_options_<?= /* @noEscape */ $entityId ?>, #allow_gift_options_for_order_<?= /* @noEscape */ $entityId ?>, #allow_gift_options_for_items_<?= /* @noEscape */ $entityId ?>": { @@ -449,6 +447,7 @@ script; break; endswitch; if ($_giftMessage): ?> +<?php // phpcs:ignore Magento2.Legacy.PhtmlTemplate ?> <script type="text/x-magento-init"> { "#shipping_method_form": { diff --git a/app/code/Magento/GiftMessageGraphQl/composer.json b/app/code/Magento/GiftMessageGraphQl/composer.json index 6d2bdbe8ab1a6..0752f71bfd6b7 100644 --- a/app/code/Magento/GiftMessageGraphQl/composer.json +++ b/app/code/Magento/GiftMessageGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-gift-message": "*" }, diff --git a/app/code/Magento/GoogleAdwords/composer.json b/app/code/Magento/GoogleAdwords/composer.json index 64ccdf3fd375e..d815cac0f2a75 100644 --- a/app/code/Magento/GoogleAdwords/composer.json +++ b/app/code/Magento/GoogleAdwords/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-store": "*" diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index b5917407b60ae..0370174c0b7f4 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -17,8 +17,6 @@ class Ga extends \Magento\Framework\View\Element\Template { /** - * Google analytics data - * * @var \Magento\GoogleAnalytics\Helper\Data */ protected $_googleAnalyticsData = null; @@ -261,8 +259,8 @@ public function getOrdersTrackingData() private function getOptPageUrl() { $optPageURL = ''; - $pageName = trim($this->getPageName()); - if ($pageName && substr($pageName, 0, 1) == '/' && strlen($pageName) > 1) { + $pageName = $this->getPageName() !== null ? trim($this->getPageName()) : ''; + if ($pageName && substr($pageName, 0, 1) === '/' && strlen($pageName) > 1) { $optPageURL = ", '" . $this->escapeHtmlAttr($pageName, false) . "'"; } return $optPageURL; diff --git a/app/code/Magento/GoogleAnalytics/composer.json b/app/code/Magento/GoogleAnalytics/composer.json index ae6310a3699b7..c2dc5c9643289 100644 --- a/app/code/Magento/GoogleAnalytics/composer.json +++ b/app/code/Magento/GoogleAnalytics/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cookie": "*", "magento/module-sales": "*", diff --git a/app/code/Magento/GoogleOptimizer/composer.json b/app/code/Magento/GoogleOptimizer/composer.json index 8642407f7da3c..d8d979d0d19cd 100644 --- a/app/code/Magento/GoogleOptimizer/composer.json +++ b/app/code/Magento/GoogleOptimizer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GraphQl/composer.json b/app/code/Magento/GraphQl/composer.json index 0e0b1546c5b93..44099e5fde5c3 100644 --- a/app/code/Magento/GraphQl/composer.json +++ b/app/code/Magento/GraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-eav": "*", "magento/framework": "*", "magento/module-webapi": "*", diff --git a/app/code/Magento/GraphQl/etc/adminhtml/system.xml b/app/code/Magento/GraphQl/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..55880c94eeafe --- /dev/null +++ b/app/code/Magento/GraphQl/etc/adminhtml/system.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<!-- +/** + * Representation of Webapi module in System Configuration (Magento admin panel). + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="webapi"> + <group id="graphql_validation" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>GraphQl Input Limits</label> + <field id="input_limit_enabled" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <label>Enable Input Limits</label> + <config_path>graphql/validation/input_limit_enabled</config_path> + </field> + <field id="maximum_page_size" translate="label comment" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Maximum Page Size</label> + <comment>Maximum number of items allowed in a paginated search result.</comment> + <config_path>graphql/validation/maximum_page_size</config_path> + <depends> + <field id="input_limit_enabled">1</field> + </depends> + </field> + </group> + </section> + </system> +</config> + diff --git a/app/code/Magento/GraphQlCache/composer.json b/app/code/Magento/GraphQlCache/composer.json index 1347ef4352eed..4b1e8945f2343 100644 --- a/app/code/Magento/GraphQlCache/composer.json +++ b/app/code/Magento/GraphQlCache/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-page-cache": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/GroupedCatalogInventory/composer.json b/app/code/Magento/GroupedCatalogInventory/composer.json index e5e882cf6c33f..0504bdbbd5bf7 100644 --- a/app/code/Magento/GroupedCatalogInventory/composer.json +++ b/app/code/Magento/GroupedCatalogInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/GroupedImportExport/composer.json b/app/code/Magento/GroupedImportExport/composer.json index e6abc70649685..614ab189ee0f2 100644 --- a/app/code/Magento/GroupedImportExport/composer.json +++ b/app/code/Magento/GroupedImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/GroupedProduct/composer.json b/app/code/Magento/GroupedProduct/composer.json index 2b5bed052ff0e..530c58a27a98c 100644 --- a/app/code/Magento/GroupedProduct/composer.json +++ b/app/code/Magento/GroupedProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GroupedProductGraphQl/composer.json b/app/code/Magento/GroupedProductGraphQl/composer.json index c446ff2e41a7d..728951688deff 100644 --- a/app/code/Magento/GroupedProductGraphQl/composer.json +++ b/app/code/Magento/GroupedProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-grouped-product": "*", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php index fba7c6860bbb5..d087ef724788b 100644 --- a/app/code/Magento/ImportExport/Model/Import.php +++ b/app/code/Magento/ImportExport/Model/Import.php @@ -51,70 +51,70 @@ */ class Import extends AbstractModel { - const BEHAVIOR_APPEND = 'append'; - const BEHAVIOR_ADD_UPDATE = 'add_update'; - const BEHAVIOR_REPLACE = 'replace'; - const BEHAVIOR_DELETE = 'delete'; - const BEHAVIOR_CUSTOM = 'custom'; + public const BEHAVIOR_APPEND = 'append'; + public const BEHAVIOR_ADD_UPDATE = 'add_update'; + public const BEHAVIOR_REPLACE = 'replace'; + public const BEHAVIOR_DELETE = 'delete'; + public const BEHAVIOR_CUSTOM = 'custom'; /** * Import source file. */ - const FIELD_NAME_SOURCE_FILE = 'import_file'; + public const FIELD_NAME_SOURCE_FILE = 'import_file'; /** * Import image archive. */ - const FIELD_NAME_IMG_ARCHIVE_FILE = 'import_image_archive'; + public const FIELD_NAME_IMG_ARCHIVE_FILE = 'import_image_archive'; /** * Import images file directory. */ - const FIELD_NAME_IMG_FILE_DIR = 'import_images_file_dir'; + public const FIELD_NAME_IMG_FILE_DIR = 'import_images_file_dir'; /** * Allowed errors count field name */ - const FIELD_NAME_ALLOWED_ERROR_COUNT = 'allowed_error_count'; + public const FIELD_NAME_ALLOWED_ERROR_COUNT = 'allowed_error_count'; /** * Validation startegt field name */ - const FIELD_NAME_VALIDATION_STRATEGY = 'validation_strategy'; + public const FIELD_NAME_VALIDATION_STRATEGY = 'validation_strategy'; /** * Import field separator. */ - const FIELD_FIELD_SEPARATOR = '_import_field_separator'; + public const FIELD_FIELD_SEPARATOR = '_import_field_separator'; /** * Import multiple value separator. */ - const FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR = '_import_multiple_value_separator'; + public const FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR = '_import_multiple_value_separator'; /** * Import empty attribute value constant. */ - const FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT = '_import_empty_attribute_value_constant'; + public const FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT = '_import_empty_attribute_value_constant'; /** * Allow multiple values wrapping in double quotes for additional attributes. */ - const FIELDS_ENCLOSURE = 'fields_enclosure'; + public const FIELDS_ENCLOSURE = 'fields_enclosure'; /** * default delimiter for several values in one cell as default for FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR */ - const DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR = ','; + public const DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR = ','; /** * Import empty attribute default value */ - const DEFAULT_EMPTY_ATTRIBUTE_VALUE_CONSTANT = '__EMPTY__VALUE__'; - const DEFAULT_SIZE = 50; - const MAX_IMPORT_CHUNKS = 4; - const IMPORT_HISTORY_DIR = 'import_history/'; - const IMPORT_DIR = 'import/'; + public const DEFAULT_EMPTY_ATTRIBUTE_VALUE_CONSTANT = '__EMPTY__VALUE__'; + public const DEFAULT_SIZE = 50; + public const MAX_IMPORT_CHUNKS = 4; + public const IMPORT_HISTORY_DIR = 'import_history/'; + public const IMPORT_DIR = 'import/'; /** * @var AbstractEntity|ImportAbstractEntity @@ -122,8 +122,6 @@ class Import extends AbstractModel protected $_entityAdapter; /** - * Import export data - * * @var DataHelper */ protected $_importExportData = null; @@ -134,7 +132,6 @@ class Import extends AbstractModel private $_coreConfig; /** - * @var \Magento\ImportExport\Model\Import\ConfigInterface * @var ConfigInterface */ protected $_importConfig; @@ -577,10 +574,14 @@ public function uploadSource() throw new LocalizedException(__('The file cannot be uploaded.')); } - // phpcs:disable Magento2.Functions.DiscouragedFunction.Discouraged - $extension = pathinfo($result['file'], PATHINFO_EXTENSION); + $extension = ''; + $uploadedFile = ''; + if ($result !== false) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $extension = pathinfo($result['file'], PATHINFO_EXTENSION); + $uploadedFile = $result['path'] . $result['file']; + } - $uploadedFile = $result['path'] . $result['file']; if (!$extension) { $this->_varDirectory->delete($uploadedFile); throw new LocalizedException(__('The file you uploaded has no extension.')); @@ -638,10 +639,11 @@ public function uploadFileAndGetSource() */ protected function _removeBom($sourceFile) { - $string = $this->_varDirectory->readFile($this->_varDirectory->getRelativePath($sourceFile)); + $driver = $this->_varDirectory->getDriver(); + $string = $driver->fileGetContents($this->_varDirectory->getAbsolutePath($sourceFile)); if ($string !== false && substr($string, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) { $string = substr($string, 3); - $this->_varDirectory->writeFile($this->_varDirectory->getRelativePath($sourceFile), $string); + $driver->filePutContents($this->_varDirectory->getAbsolutePath($sourceFile), $string); } return $this; } diff --git a/app/code/Magento/ImportExport/Model/Import/AbstractSource.php b/app/code/Magento/ImportExport/Model/Import/AbstractSource.php index 41665a71341c1..57fa361ab2f63 100644 --- a/app/code/Magento/ImportExport/Model/Import/AbstractSource.php +++ b/app/code/Magento/ImportExport/Model/Import/AbstractSource.php @@ -7,6 +7,7 @@ use Magento\ImportExport\Model\Import\AbstractEntity; +// phpcs:disable Magento2.Classes.AbstractApi /** * Data source with columns for Magento_ImportExport * @@ -83,6 +84,7 @@ public function getColNames() * * @return array */ + #[\ReturnTypeWillChange] public function current() { $row = $this->_row; @@ -101,6 +103,7 @@ public function current() * * @return void */ + #[\ReturnTypeWillChange] public function next() { $this->_key++; @@ -127,6 +130,7 @@ abstract protected function _getNextRow(); * * @return int -1 if out of bounds, 0 or more otherwise */ + #[\ReturnTypeWillChange] public function key() { return $this->_key; @@ -137,6 +141,7 @@ public function key() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return -1 !== $this->_key; @@ -147,6 +152,7 @@ public function valid() * * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->_key = -1; @@ -161,6 +167,7 @@ public function rewind() * @return void * @throws \OutOfBoundsException */ + #[\ReturnTypeWillChange] public function seek($position) { if ($position == $this->_key) { diff --git a/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php b/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php index 6e77016adf361..254050bfb1dc1 100644 --- a/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php +++ b/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php @@ -56,6 +56,7 @@ protected function _construct() * * @return \Iterator */ + #[\ReturnTypeWillChange] public function getIterator() { $connection = $this->getConnection(); diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml index 25331ae3cd058..17b065ec7d88e 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml @@ -58,7 +58,10 @@ <argument name="attributeCode" value="$$productAttribute.attribute_code$$"/> </actionGroup> <!--Check that attribute value is selected--> - <scrollTo selector="{{AdminProductFormSection.attributeTab}}" stepKey="scrollToAttributeTitle1"/> + <waitForElement selector="{{AdminProductFormSection.attributeTab}}" stepKey="waitForSection1"/> + <executeJS function="return document.evaluate("{{AdminProductFormSection.attributeTab}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition1"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight1"/> + <executeJS function="window.scrollTo({top: {$sectionPosition1}-{$floatingHeaderHeight1}})" stepKey="scrollToAttributeTitle1"/> <conditionalClick selector="{{AdminProductFormSection.attributeTab}}" dependentSelector="{{AdminProductAttributeSection.dropDownAttribute($$productAttribute.attribute_code$$)}}" visible="false" stepKey="expandAttributeTab1"/> <seeOptionIsSelected selector="{{AdminProductAttributeSection.dropDownAttribute($$productAttribute.attribute_code$$)}}" userInput="option2" stepKey="seeAttributeValueIsSelected1"/> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> @@ -72,7 +75,10 @@ <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="filterAndSelectTheProduct2"> <argument name="productSku" value="{{simpleProductWithShortNameAndSku.sku}}"/> </actionGroup> - <scrollTo selector="{{AdminProductFormSection.attributeTab}}" stepKey="scrollToAttributeTitle2"/> + <waitForElement selector="{{AdminProductFormSection.attributeTab}}" stepKey="waitForSection2"/> + <executeJS function="return document.evaluate("{{AdminProductFormSection.attributeTab}}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect().y" stepKey="sectionPosition2"/> + <executeJS function="return document.querySelector("{{AdminHeaderSection.pageMainActions}}").getBoundingClientRect().height" stepKey="floatingHeaderHeight2"/> + <executeJS function="window.scrollTo({top: {$sectionPosition2}-{$floatingHeaderHeight2}})" stepKey="scrollToAttributeTitle2"/> <conditionalClick selector="{{AdminProductFormSection.attributeTab}}" dependentSelector="{{AdminProductAttributeSection.dropDownAttribute($$productAttribute.attribute_code$$)}}" visible="false" stepKey="expandAttributeTab2"/> <seeOptionIsSelected selector="{{AdminProductAttributeSection.dropDownAttribute($$productAttribute.attribute_code$$)}}" userInput="" stepKey="seeAttributeValueIsSelected2"/> </test> diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json index 6a591f98378b3..70dfdf1f6ec98 100644 --- a/app/code/Magento/ImportExport/composer.json +++ b/app/code/Magento/ImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-ctype": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Indexer/composer.json b/app/code/Magento/Indexer/composer.json index 6c92b5989ef36..5a0ca5fb03643 100644 --- a/app/code/Magento/Indexer/composer.json +++ b/app/code/Magento/Indexer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/InstantPurchase/composer.json b/app/code/Magento/InstantPurchase/composer.json index b26abc22cfc34..900b2d1204ade 100644 --- a/app/code/Magento/InstantPurchase/composer.json +++ b/app/code/Magento/InstantPurchase/composer.json @@ -7,7 +7,7 @@ "AFL-3.0" ], "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Integration/composer.json b/app/code/Magento/Integration/composer.json index b7bd82d4e7ce2..34872f8e426e4 100644 --- a/app/code/Magento/Integration/composer.json +++ b/app/code/Magento/Integration/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/JwtFrameworkAdapter/composer.json b/app/code/Magento/JwtFrameworkAdapter/composer.json index 18aa66db4d1a1..3f8aa56814712 100644 --- a/app/code/Magento/JwtFrameworkAdapter/composer.json +++ b/app/code/Magento/JwtFrameworkAdapter/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "web-token/jwt-framework": "^v2.2.7" }, diff --git a/app/code/Magento/JwtUserToken/composer.json b/app/code/Magento/JwtUserToken/composer.json index 017df858d5509..9af264d5eef21 100644 --- a/app/code/Magento/JwtUserToken/composer.json +++ b/app/code/Magento/JwtUserToken/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-integration": "*", "magento/module-authorization": "*" diff --git a/app/code/Magento/LayeredNavigation/composer.json b/app/code/Magento/LayeredNavigation/composer.json index c07bb7f7e9ea6..115a206e2b546 100644 --- a/app/code/Magento/LayeredNavigation/composer.json +++ b/app/code/Magento/LayeredNavigation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*" diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerPlaceOrderTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerPlaceOrderTest.xml index 548f3637b5973..705756bd039d5 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerPlaceOrderTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerPlaceOrderTest.xml @@ -78,7 +78,8 @@ </actionGroup> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openCart"/> <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderId"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Assert Storefront Order page contains message about Order created by a Store Administrator --> <actionGroup ref="StorefrontAssertContainsMessageOrderCreatedByAdminActionGroup" stepKey="verifyStorefrontMessageOrderCreatedByAdmin"> diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml index 4ddf500dec37e..e570b833b3cdf 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml @@ -92,7 +92,8 @@ <argument name="orderNumber" value="{$grabOrderId}"/> </actionGroup> <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeReorder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabReorderId"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabReorderId"/> <!-- Assert Storefront Order page contains message about Order created by a Store Administrator --> <actionGroup ref="StorefrontAssertContainsMessageOrderCreatedByAdminActionGroup" stepKey="verifyStorefrontMessageOrderCreatedByAdmin"> diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminNoAccessToLoginAsCustomerButtonTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminNoAccessToLoginAsCustomerButtonTest.xml index 484fb14705c6c..396eaddbee49c 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminNoAccessToLoginAsCustomerButtonTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminNoAccessToLoginAsCustomerButtonTest.xml @@ -88,7 +88,7 @@ <argument name="product" value="$$createSimpleProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="{{AdminOrderDetailsInformationSection.orderId}}" stepKey="grabOrderId"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Verify Login as Customer Login action is absent on Order page --> <actionGroup ref="AdminLoginAsCustomerAbsentOnOrderPageActionGroup" stepKey="verifyLoginAsCustomerAbsentOnOrderPage"> diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminUINotShownIfLoginAsCustomerDisabledTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminUINotShownIfLoginAsCustomerDisabledTest.xml index 10bff7c2ef68f..e27acda86a4a2 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminUINotShownIfLoginAsCustomerDisabledTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminUINotShownIfLoginAsCustomerDisabledTest.xml @@ -44,7 +44,7 @@ <argument name="product" value="$$createSimpleProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="{{AdminOrderDetailsInformationSection.orderId}}" stepKey="grabOrderId"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <!-- Verify Login as Customer Login action is absent on Order page --> <actionGroup ref="AdminLoginAsCustomerAbsentOnOrderPageActionGroup" stepKey="verifyLoginAsCustomerAbsentOnOrderPage"> diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerSeeSpecialPriceOnCategoryTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerSeeSpecialPriceOnCategoryTest.xml index a100146deb8f3..ceabae916f931 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerSeeSpecialPriceOnCategoryTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerSeeSpecialPriceOnCategoryTest.xml @@ -92,12 +92,13 @@ </actionGroup> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openCart"/> <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderId"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <closeTab stepKey="closeLoginAsCustomerTab"/> <!-- Open order in admin --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="addFilterToGridAndOpenOrder"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="addFilterToGridAndOpenOrder"> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> <!-- Assert order subtotal --> diff --git a/app/code/Magento/LoginAsCustomer/composer.json b/app/code/Magento/LoginAsCustomer/composer.json index 8bbcfec48b2b0..6a16c4a394454 100755 --- a/app/code/Magento/LoginAsCustomer/composer.json +++ b/app/code/Magento/LoginAsCustomer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer", "description": "Allow for admin to enter a customer account", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerAdminUi/composer.json b/app/code/Magento/LoginAsCustomerAdminUi/composer.json index 1deba8bc0bd0d..b4b9dc022f2e2 100644 --- a/app/code/Magento/LoginAsCustomerAdminUi/composer.json +++ b/app/code/Magento/LoginAsCustomerAdminUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-admin-ui", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-login-as-customer-frontend-ui": "*", diff --git a/app/code/Magento/LoginAsCustomerApi/composer.json b/app/code/Magento/LoginAsCustomerApi/composer.json index 99e6633836084..3f11d114d0ddb 100644 --- a/app/code/Magento/LoginAsCustomerApi/composer.json +++ b/app/code/Magento/LoginAsCustomerApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-api", "description": "Allow for admin to enter a customer account", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/LoginAsCustomerAssistance/composer.json b/app/code/Magento/LoginAsCustomerAssistance/composer.json index a72a498e30c36..77e3eac3c758b 100644 --- a/app/code/Magento/LoginAsCustomerAssistance/composer.json +++ b/app/code/Magento/LoginAsCustomerAssistance/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-assistance", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/LoginAsCustomerFrontendUi/composer.json b/app/code/Magento/LoginAsCustomerFrontendUi/composer.json index 7ea03a41b0475..436d59d084b3d 100644 --- a/app/code/Magento/LoginAsCustomerFrontendUi/composer.json +++ b/app/code/Magento/LoginAsCustomerFrontendUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-frontend-ui", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerGraphQl/composer.json b/app/code/Magento/LoginAsCustomerGraphQl/composer.json index 1ea5f9ad5a7c4..35f7e256c81d4 100755 --- a/app/code/Magento/LoginAsCustomerGraphQl/composer.json +++ b/app/code/Magento/LoginAsCustomerGraphQl/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-graph-ql", "description": "Flexible login as a customer so a merchant or merchant admin can log into an end customer's account to assist them with their account.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-login-as-customer-assistance": "*", diff --git a/app/code/Magento/LoginAsCustomerLog/composer.json b/app/code/Magento/LoginAsCustomerLog/composer.json index 725802cadb19a..8c62321304a38 100644 --- a/app/code/Magento/LoginAsCustomerLog/composer.json +++ b/app/code/Magento/LoginAsCustomerLog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-log", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerPageCache/composer.json b/app/code/Magento/LoginAsCustomerPageCache/composer.json index fc0c7ee5a2e3f..8f72e3317493c 100644 --- a/app/code/Magento/LoginAsCustomerPageCache/composer.json +++ b/app/code/Magento/LoginAsCustomerPageCache/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-page-cache", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-login-as-customer-api": "*" diff --git a/app/code/Magento/LoginAsCustomerQuote/composer.json b/app/code/Magento/LoginAsCustomerQuote/composer.json index aa42437221298..bc158dd3f0d99 100644 --- a/app/code/Magento/LoginAsCustomerQuote/composer.json +++ b/app/code/Magento/LoginAsCustomerQuote/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-quote", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerSales/composer.json b/app/code/Magento/LoginAsCustomerSales/composer.json index 9c67a1cd5ae56..a5d0636876083 100644 --- a/app/code/Magento/LoginAsCustomerSales/composer.json +++ b/app/code/Magento/LoginAsCustomerSales/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-sales", "description": "", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-user": "*", diff --git a/app/code/Magento/Marketplace/composer.json b/app/code/Magento/Marketplace/composer.json index 5de30732e24c2..825f04bc77556 100644 --- a/app/code/Magento/Marketplace/composer.json +++ b/app/code/Magento/Marketplace/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/MediaContent/composer.json b/app/code/Magento/MediaContent/composer.json index 57c1addd28c1d..840be80169869 100644 --- a/app/code/Magento/MediaContent/composer.json +++ b/app/code/Magento/MediaContent/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content", "description": "Magento module provides the implementation for managing relations between content and media files used in that content", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-content-api": "*", "magento/module-media-gallery-api": "*" diff --git a/app/code/Magento/MediaContentApi/composer.json b/app/code/Magento/MediaContentApi/composer.json index 60dcacf0e26d5..65769c344d89c 100644 --- a/app/code/Magento/MediaContentApi/composer.json +++ b/app/code/Magento/MediaContentApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-api", "description": "Magento module provides the API interfaces for managing relations between content and media files used in that content", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-media-gallery-api": "*", "magento/framework": "*" }, diff --git a/app/code/Magento/MediaContentCatalog/composer.json b/app/code/Magento/MediaContentCatalog/composer.json index edfe76bf626b2..848d042a2990a 100644 --- a/app/code/Magento/MediaContentCatalog/composer.json +++ b/app/code/Magento/MediaContentCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-catalog", "description": "Magento module provides the implementation of MediaContent functionality for Magento_Catalog module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-media-content-api": "*", "magento/module-catalog": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/MediaContentCms/composer.json b/app/code/Magento/MediaContentCms/composer.json index c9c3fba0cd987..216a3ac7708ad 100644 --- a/app/code/Magento/MediaContentCms/composer.json +++ b/app/code/Magento/MediaContentCms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-cms", "description": "Magento module provides the implementation of MediaContent functionality for Magento_Cms module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-media-content-api": "*", "magento/module-cms": "*", "magento/framework": "*" diff --git a/app/code/Magento/MediaContentSynchronization/composer.json b/app/code/Magento/MediaContentSynchronization/composer.json index 23e64526b6357..9ed6c2aee74e3 100644 --- a/app/code/Magento/MediaContentSynchronization/composer.json +++ b/app/code/Magento/MediaContentSynchronization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization", "description": "Magento module provides implementation of the media content data synchronization.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-media-content-synchronization-api": "*", diff --git a/app/code/Magento/MediaContentSynchronizationApi/composer.json b/app/code/Magento/MediaContentSynchronizationApi/composer.json index 5deba419c5ece..595cdd43b8bfa 100644 --- a/app/code/Magento/MediaContentSynchronizationApi/composer.json +++ b/app/code/Magento/MediaContentSynchronizationApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-api", "description": "Magento module responsible for the media content synchronization implementation API", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-content-api": "*" }, diff --git a/app/code/Magento/MediaContentSynchronizationCatalog/composer.json b/app/code/Magento/MediaContentSynchronizationCatalog/composer.json index e5b05b7ede7ca..653e6b3e98c0a 100644 --- a/app/code/Magento/MediaContentSynchronizationCatalog/composer.json +++ b/app/code/Magento/MediaContentSynchronizationCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-catalog", "description": "Magento module provides the implementation of MediaContentSynchronization functionality for Magento_Catalog module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-content-synchronization-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaContentSynchronizationCms/composer.json b/app/code/Magento/MediaContentSynchronizationCms/composer.json index 6ad621cd69c27..753f7d51159b3 100644 --- a/app/code/Magento/MediaContentSynchronizationCms/composer.json +++ b/app/code/Magento/MediaContentSynchronizationCms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-cms", "description": "Magento module provides the implementation of MediaContentSynchronization functionality for Magento_Cms module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-content-synchronization-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaGallery/composer.json b/app/code/Magento/MediaGallery/composer.json index 6f2bca9a6d2b1..4aab7849a3539 100644 --- a/app/code/Magento/MediaGallery/composer.json +++ b/app/code/Magento/MediaGallery/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery", "description": "Magento module responsible for media handling", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-cms": "*" diff --git a/app/code/Magento/MediaGalleryApi/composer.json b/app/code/Magento/MediaGalleryApi/composer.json index dc1dc5dd73146..32ec51e271a92 100644 --- a/app/code/Magento/MediaGalleryApi/composer.json +++ b/app/code/Magento/MediaGalleryApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-api", "description": "Magento module responsible for media gallery asset attributes storage and management", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGalleryCatalog/composer.json b/app/code/Magento/MediaGalleryCatalog/composer.json index 42c91e19c3994..d14b11b7dd204 100644 --- a/app/code/Magento/MediaGalleryCatalog/composer.json +++ b/app/code/Magento/MediaGalleryCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog", "description": "Magento module responsible for catalog gallery processor delete operation handling", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-catalog": "*" diff --git a/app/code/Magento/MediaGalleryCatalogIntegration/composer.json b/app/code/Magento/MediaGalleryCatalogIntegration/composer.json index 5e03d97b69b2a..1462dadfdade3 100644 --- a/app/code/Magento/MediaGalleryCatalogIntegration/composer.json +++ b/app/code/Magento/MediaGalleryCatalogIntegration/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog-integration", "description": "Magento module responsible for extending catalog image uploader functionality", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryCatalogUi/composer.json b/app/code/Magento/MediaGalleryCatalogUi/composer.json index 1252399e59978..33a2805f5aa1b 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/composer.json +++ b/app/code/Magento/MediaGalleryCatalogUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog-ui", "description": "Magento module that implement category grid for media gallery.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/MediaGalleryCmsUi/composer.json b/app/code/Magento/MediaGalleryCmsUi/composer.json index ca2cf542f98ae..946a9f221ec86 100644 --- a/app/code/Magento/MediaGalleryCmsUi/composer.json +++ b/app/code/Magento/MediaGalleryCmsUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-cms-ui", "description": "Cms related UI elements in the magento media gallery", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-backend": "*" diff --git a/app/code/Magento/MediaGalleryIntegration/composer.json b/app/code/Magento/MediaGalleryIntegration/composer.json index 522e5ba1f368a..8d8146c751adf 100644 --- a/app/code/Magento/MediaGalleryIntegration/composer.json +++ b/app/code/Magento/MediaGalleryIntegration/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-integration", "description": "Magento module responsible for integration of enhanced media gallery", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-ui-api": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryMetadata/Model/AddIptcMetadata.php b/app/code/Magento/MediaGalleryMetadata/Model/AddIptcMetadata.php index 9935904468388..2d16b928ef4bb 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/AddIptcMetadata.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/AddIptcMetadata.php @@ -7,7 +7,10 @@ namespace Magento\MediaGalleryMetadata\Model; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\Jpeg\ReadFile; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; @@ -39,19 +42,31 @@ class AddIptcMetadata */ private $fileFactory; + /** + * @var IptcEmbed + */ + private $iptcEmbed; + /** * @param FileInterfaceFactory $fileFactory * @param DriverInterface $driver * @param ReadFile $fileReader + * @param Filesystem|null $filesystem + * @param IptcEmbed|null $iptcEmbed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( FileInterfaceFactory $fileFactory, DriverInterface $driver, - ReadFile $fileReader + ReadFile $fileReader, + Filesystem $filesystem = null, + IptcEmbed $iptcEmbed = null ) { $this->fileFactory = $fileFactory; - $this->driver = $driver; $this->fileReader = $fileReader; + $filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); + $this->driver = $filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); + $this->iptcEmbed = $iptcEmbed ?? ObjectManager::getInstance()->get(IptcEmbed::class); } /** @@ -89,7 +104,7 @@ public function execute(FileInterface $file, MetadataInterface $metadata, ?Segme } } - $this->writeFile($file->getPath(), iptcembed($newData, $file->getPath())); + $this->writeFile($file->getPath(), $this->iptcEmbed->get($newData, $file->getPath())); $fileWithIptc = $this->fileReader->execute($file->getPath()); diff --git a/app/code/Magento/MediaGalleryMetadata/Model/ExifReader.php b/app/code/Magento/MediaGalleryMetadata/Model/ExifReader.php new file mode 100644 index 0000000000000..da8731c18d015 --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/ExifReader.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model; + +use Magento\Framework\Exception\LocalizedException; + +/** + * Wrapper for the exif_read_data php function + */ +class ExifReader +{ + /** + * Returns result of exif_read_data function + * + * @param string $filePath + * @return array|false + * @throws LocalizedException + */ + public function get(string $filePath) + { + if (!is_callable('exif_read_data')) { + throw new LocalizedException( + __('exif_read_data() must be enabled in php configuration') + ); + } + + return exif_read_data($filePath); + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Gif/ReadFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Gif/ReadFile.php index 88810d3ccf28f..7479030247639 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Gif/ReadFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Gif/ReadFile.php @@ -7,8 +7,10 @@ namespace Magento\MediaGalleryMetadata\Model\Gif; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\SegmentNames; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -17,6 +19,7 @@ use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; use Magento\MediaGalleryMetadataApi\Model\SegmentInterfaceFactory; use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem; /** * File segments reader @@ -28,6 +31,11 @@ class ReadFile implements ReadFileInterface */ private $driver; + /** + * @var Filesystem + */ + private $filesystem; + /** * @var SegmentInterfaceFactory */ @@ -48,17 +56,20 @@ class ReadFile implements ReadFileInterface * @param FileInterfaceFactory $fileFactory * @param SegmentInterfaceFactory $segmentFactory * @param SegmentNames $segmentNames + * @param Filesystem|null $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, FileInterfaceFactory $fileFactory, SegmentInterfaceFactory $segmentFactory, - SegmentNames $segmentNames + SegmentNames $segmentNames, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->fileFactory = $fileFactory; $this->segmentFactory = $segmentFactory; $this->segmentNames = $segmentNames; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -66,19 +77,19 @@ public function __construct( */ public function execute(string $path): FileInterface { - $resource = $this->driver->fileOpen($path, 'rb'); + $resource = $this->getDriver()->fileOpen($path, 'rb'); $header = $this->read($resource, 3); if ($header != "GIF") { - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); throw new ValidatorException(__('Not a GIF image')); } $version = $this->read($resource, 3); if (!in_array($version, ['87a', '89a'])) { - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); throw new LocalizedException(__('Unexpected GIF version')); } @@ -103,6 +114,8 @@ public function execute(string $path): FileInterface array_unshift($segments, $headerSegment, $generalSegment); + $this->getDriver()->fileClose($resource); + return $this->fileFactory->create([ 'path' => $path, 'segments' => $segments @@ -143,7 +156,7 @@ private function getSegments($resource): array } $segments[] = $this->getExtensionSegment($resource); - } while (!$this->driver->endOfFile($resource)); + } while (!$this->getDriver()->endOfFile($resource)); return $segments; } @@ -272,8 +285,8 @@ private function read($resource, int $length): string { $data = ''; - while (!$this->driver->endOfFile($resource) && strlen($data) < $length) { - $data .= $this->driver->fileRead($resource, $length - strlen($data)); + while (!$this->getDriver()->endOfFile($resource) && strlen($data) < $length) { + $data .= $this->getDriver()->fileRead($resource, $length - strlen($data)); } return $data; @@ -315,4 +328,19 @@ private function readBlock($resource): string } return $blockLengthBinary . $this->read($resource, $blockLength) . $this->read($resource, 1); } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); + } + + return $this->driver; + } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Gif/WriteFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Gif/WriteFile.php index cbdc9fa286e85..b472e39694203 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Gif/WriteFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Gif/WriteFile.php @@ -7,8 +7,11 @@ namespace Magento\MediaGalleryMetadata\Model\Gif; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\SegmentNames; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -30,16 +33,24 @@ class WriteFile implements WriteFileInterface */ private $segmentNames; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param DriverInterface $driver * @param SegmentNames $segmentNames + * @param Filesystem|null $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, - SegmentNames $segmentNames + SegmentNames $segmentNames, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->segmentNames = $segmentNames; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -51,10 +62,10 @@ public function __construct( */ public function execute(FileInterface $file): void { - $resource = $this->driver->fileOpen($file->getPath(), 'wb'); + $resource = $this->getDriver()->fileOpen($file->getPath(), 'wb'); $this->writeSegments($resource, $file->getSegments()); - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); } /** @@ -66,11 +77,26 @@ public function execute(FileInterface $file): void private function writeSegments($resource, array $segments): void { foreach ($segments as $segment) { - $this->driver->fileWrite( + $this->getDriver()->fileWrite( $resource, $segment->getData() ); } - $this->driver->fileWrite($resource, pack("C", ord(";"))); + $this->getDriver()->fileWrite($resource, pack("C", ord(";"))); + } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); + } + + return $this->driver; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/IptcEmbed.php b/app/code/Magento/MediaGalleryMetadata/Model/IptcEmbed.php new file mode 100644 index 0000000000000..0be8911397b5a --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/IptcEmbed.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model; + +use Magento\Framework\Exception\LocalizedException; + +/** + * Wrapper for native iptcembed function + */ +class IptcEmbed +{ + /** + * Returns result of iptcembed function + * + * @param string $iptcData + * @param string $filePath + * @throws LocalizedException if iptcembed function is not enabled + */ + public function get(string $iptcData, string $filePath) + { + if (!is_callable('iptcembed')) { + throw new LocalizedException(__('iptcembed() must be enabled in php configuration')); + } + + return iptcembed($iptcData, $filePath); + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/ReadFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/ReadFile.php index ed241d03506c1..56afb2f4f18ab 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/ReadFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/ReadFile.php @@ -7,9 +7,12 @@ namespace Magento\MediaGalleryMetadata\Model\Jpeg; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\SegmentNames; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -36,6 +39,11 @@ class ReadFile implements ReadFileInterface */ private $driver; + /** + * @var Filesystem + */ + private $filesystem; + /** * @var SegmentInterfaceFactory */ @@ -56,17 +64,20 @@ class ReadFile implements ReadFileInterface * @param FileInterfaceFactory $fileFactory * @param SegmentInterfaceFactory $segmentFactory * @param SegmentNames $segmentNames + * @param Filesystem|null $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, FileInterfaceFactory $fileFactory, SegmentInterfaceFactory $segmentFactory, - SegmentNames $segmentNames + SegmentNames $segmentNames, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->fileFactory = $fileFactory; $this->segmentFactory = $segmentFactory; $this->segmentNames = $segmentNames; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -78,13 +89,13 @@ public function __construct( */ private function isApplicable(string $path): bool { - $resource = $this->driver->fileOpen($path, 'rb'); + $resource = $this->getDriver()->fileOpen($path, 'rb'); try { $marker = $this->readMarker($resource); } catch (LocalizedException $exception) { return false; } - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); return $marker == self::MARKER_IMAGE_FILE_START; } @@ -98,18 +109,18 @@ public function execute(string $path): FileInterface throw new ValidatorException(__('Not a JPEG image')); } - $resource = $this->driver->fileOpen($path, 'rb'); + $resource = $this->getDriver()->fileOpen($path, 'rb'); $marker = $this->readMarker($resource); if ($marker != self::MARKER_IMAGE_FILE_START) { - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); throw new ValidatorException(__('Not a JPEG image')); } do { $marker = $this->readMarker($resource); $segments[] = $this->readSegment($resource, ord($marker)); - } while (($marker != self::MARKER_IMAGE_START) && (!$this->driver->endOfFile($resource))); + } while (($marker != self::MARKER_IMAGE_START) && (!$this->getDriver()->endOfFile($resource))); if ($marker != self::MARKER_IMAGE_START) { throw new LocalizedException(__('File is corrupted')); @@ -120,7 +131,7 @@ public function execute(string $path): FileInterface 'data' => $this->readCompressedImage($resource) ]); - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); return $this->fileFactory->create([ 'path' => $path, @@ -140,7 +151,7 @@ private function readMarker($resource): string $data = $this->read($resource, self::TWO_BYTES); if ($data[0] != self::MARKER_PREFIX) { - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); throw new LocalizedException(__('File is corrupted')); } @@ -159,7 +170,7 @@ private function readCompressedImage($resource): string $compressedImage = ''; do { $compressedImage .= $this->read($resource, self::ONE_MEGABYTE); - } while (!$this->driver->endOfFile($resource)); + } while (!$this->getDriver()->endOfFile($resource)); $endOfImageMarkerPosition = strpos($compressedImage, self::MARKER_PREFIX . self::MARKER_IMAGE_END); @@ -200,10 +211,25 @@ private function read($resource, int $length): string { $data = ''; - while (!$this->driver->endOfFile($resource) && strlen($data) < $length) { - $data .= $this->driver->fileRead($resource, $length - strlen($data)); + while (!$this->getDriver()->endOfFile($resource) && strlen($data) < $length) { + $data .= $this->getDriver()->fileRead($resource, $length - strlen($data)); } return $data; } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); + } + + return $this->driver; + } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php index b6c32296f3f7d..c00b702d7f352 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -7,6 +7,8 @@ namespace Magento\MediaGalleryMetadata\Model\Jpeg\Segment; +use Magento\Framework\App\ObjectManager; +use Magento\MediaGalleryMetadata\Model\ExifReader; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -28,13 +30,21 @@ class ReadExif implements ReadMetadataInterface */ private $metadataFactory; + /** + * @var ExifReader + */ + private $exifReader; + /** * @param MetadataInterfaceFactory $metadataFactory + * @param ExifReader|null $exifReader */ public function __construct( - MetadataInterfaceFactory $metadataFactory + MetadataInterfaceFactory $metadataFactory, + ExifReader $exifReader = null ) { $this->metadataFactory = $metadataFactory; + $this->exifReader = $exifReader ?? ObjectManager::getInstance()->get(ExifReader::class); } /** @@ -62,7 +72,7 @@ public function execute(FileInterface $file): MetadataInterface } /** - * Parese exif data from segment + * Parse exif data from segment * * @param string $filePath */ @@ -72,7 +82,7 @@ private function getExifData(string $filePath): MetadataInterface $description = null; $keywords = null; - $data = exif_read_data($filePath); + $data = $this->exifReader->get($filePath); if (!empty($data)) { $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/WriteFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/WriteFile.php index 403bc7f3d7449..4dcc6404d0821 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/WriteFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/WriteFile.php @@ -7,8 +7,11 @@ namespace Magento\MediaGalleryMetadata\Model\Jpeg; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\SegmentNames; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -34,16 +37,24 @@ class WriteFile implements WriteFileInterface */ private $segmentNames; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param DriverInterface $driver * @param SegmentNames $segmentNames + * @param Filesystem $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, - SegmentNames $segmentNames + SegmentNames $segmentNames, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->segmentNames = $segmentNames; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -61,12 +72,12 @@ public function execute(FileInterface $file): void } } - $resource = $this->driver->fileOpen($file->getPath(), 'wb'); + $resource = $this->getDriver()->fileOpen($file->getPath(), 'wb'); - $this->driver->fileWrite($resource, self::MARKER_IMAGE_PREFIX . self::MARKER_IMAGE_FILE_START); + $this->getDriver()->fileWrite($resource, self::MARKER_IMAGE_PREFIX . self::MARKER_IMAGE_FILE_START); $this->writeSegments($resource, $file->getSegments()); - $this->driver->fileWrite($resource, self::MARKER_IMAGE_PREFIX . self::MARKER_IMAGE_END); - $this->driver->fileClose($resource); + $this->getDriver()->fileWrite($resource, self::MARKER_IMAGE_PREFIX . self::MARKER_IMAGE_END); + $this->getDriver()->fileClose($resource); } /** @@ -79,14 +90,29 @@ private function writeSegments($resource, array $segments): void { foreach ($segments as $segment) { if ($segment->getName() !== 'CompressedImage') { - $this->driver->fileWrite( + $this->getDriver()->fileWrite( $resource, //phpcs:ignore Magento2.Functions.DiscouragedFunction self::MARKER_IMAGE_PREFIX . chr($this->segmentNames->getSegmentType($segment->getName())) ); - $this->driver->fileWrite($resource, pack("n", strlen($segment->getData()) + 2)); + $this->getDriver()->fileWrite($resource, pack("n", strlen($segment->getData()) + 2)); } - $this->driver->fileWrite($resource, $segment->getData()); + $this->getDriver()->fileWrite($resource, $segment->getData()); + } + } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); } + + return $this->driver; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/ReadFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/ReadFile.php index 673f8ff436ebe..f47159cabfb83 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/ReadFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/ReadFile.php @@ -7,9 +7,12 @@ namespace Magento\MediaGalleryMetadata\Model\Png; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Filesystem; use Magento\MediaGalleryMetadataApi\Model\FileInterface; use Magento\MediaGalleryMetadataApi\Model\FileInterfaceFactory; use Magento\MediaGalleryMetadataApi\Model\ReadFileInterface; @@ -29,6 +32,11 @@ class ReadFile implements ReadFileInterface */ private $driver; + /** + * @var Filesystem + */ + private $filesystem; + /** * @var SegmentInterfaceFactory */ @@ -43,15 +51,18 @@ class ReadFile implements ReadFileInterface * @param DriverInterface $driver * @param FileInterfaceFactory $fileFactory * @param SegmentInterfaceFactory $segmentFactory + * @param Filesystem $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, FileInterfaceFactory $fileFactory, - SegmentInterfaceFactory $segmentFactory + SegmentInterfaceFactory $segmentFactory, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->fileFactory = $fileFactory; $this->segmentFactory = $segmentFactory; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -59,11 +70,11 @@ public function __construct( */ public function execute(string $path): FileInterface { - $resource = $this->driver->fileOpen($path, 'rb'); + $resource = $this->getDriver()->fileOpen($path, 'rb'); $header = $this->readHeader($resource); if ($header != self::PNG_FILE_START) { - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); throw new ValidatorException(__('Not a PNG image')); } @@ -83,10 +94,10 @@ public function execute(string $path): FileInterface } } while ($header && $segmentHeader['type'] != self::PNG_MARKER_IMAGE_END - && !$this->driver->endOfFile($resource) + && !$this->getDriver()->endOfFile($resource) ); - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); return $this->fileFactory->create([ 'path' => $path, @@ -118,10 +129,25 @@ private function read($resource, int $length): string { $data = ''; - while (!$this->driver->endOfFile($resource) && strlen($data) < $length) { - $data .= $this->driver->fileRead($resource, $length - strlen($data)); + while (!$this->getDriver()->endOfFile($resource) && strlen($data) < $length) { + $data .= $this->getDriver()->fileRead($resource, $length - strlen($data)); } return $data; } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); + } + + return $this->driver; + } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/WriteFile.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/WriteFile.php index c5db6644b3545..0289c47ecc227 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/WriteFile.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/WriteFile.php @@ -7,8 +7,11 @@ namespace Magento\MediaGalleryMetadata\Model\Png; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadata\Model\SegmentNames; use Magento\MediaGalleryMetadataApi\Model\FileInterface; @@ -32,16 +35,24 @@ class WriteFile implements WriteFileInterface */ private $segmentNames; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param DriverInterface $driver * @param SegmentNames $segmentNames + * @param Filesystem $filesystem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( DriverInterface $driver, - SegmentNames $segmentNames + SegmentNames $segmentNames, + Filesystem $filesystem = null ) { - $this->driver = $driver; $this->segmentNames = $segmentNames; + $this->filesystem = $filesystem ?? ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -53,11 +64,11 @@ public function __construct( */ public function execute(FileInterface $file): void { - $resource = $this->driver->fileOpen($file->getPath(), 'wb'); + $resource = $this->getDriver()->fileOpen($file->getPath(), 'wb'); - $this->driver->fileWrite($resource, self::PNG_FILE_START); + $this->getDriver()->fileWrite($resource, self::PNG_FILE_START); $this->writeSegments($resource, $file->getSegments()); - $this->driver->fileClose($resource); + $this->getDriver()->fileClose($resource); } /** @@ -69,10 +80,25 @@ public function execute(FileInterface $file): void private function writeSegments($resource, array $segments): void { foreach ($segments as $segment) { - $this->driver->fileWrite($resource, pack("N", strlen($segment->getData()))); - $this->driver->fileWrite($resource, pack("a4", $segment->getName())); - $this->driver->fileWrite($resource, $segment->getData()); - $this->driver->fileWrite($resource, pack("N", crc32($segment->getName() . $segment->getData()))); + $this->getDriver()->fileWrite($resource, pack("N", strlen($segment->getData()))); + $this->getDriver()->fileWrite($resource, pack("a4", $segment->getName())); + $this->getDriver()->fileWrite($resource, $segment->getData()); + $this->getDriver()->fileWrite($resource, pack("N", crc32($segment->getName() . $segment->getData()))); + } + } + + /** + * Returns current driver for media directory + * + * @return DriverInterface + * @throws FileSystemException + */ + private function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getDriver(); } + + return $this->driver; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/AddMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/AddMetadataTest.php index c284bf71e60af..4caa74b65f0cd 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/AddMetadataTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/AddMetadataTest.php @@ -11,7 +11,6 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; -use Magento\Framework\Filesystem\DriverInterface; use Magento\MediaGalleryMetadataApi\Api\AddMetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; @@ -31,12 +30,7 @@ class AddMetadataTest extends TestCase /** * @var WriteInterface */ - private $varDirectory; - - /** - * @var DriverInterface - */ - private $driver; + private $directory; /** * @var MetadataInterfaceFactory @@ -54,11 +48,19 @@ class AddMetadataTest extends TestCase protected function setUp(): void { $this->addMetadata = Bootstrap::getObjectManager()->get(AddMetadataInterface::class); - $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) - ->getDirectoryWrite(DirectoryList::VAR_DIR); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataInterfaceFactory::class); $this->extractMetadata = Bootstrap::getObjectManager()->get(ExtractMetadataInterface::class); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -77,11 +79,11 @@ public function testExecute( ?string $description, ?array $keywords ): void { - $path = realpath(__DIR__ . '/../../_files/' . $fileName); - $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); - $this->driver->copy( - $path, - $modifiableFilePath + $modifiableFilePath = $this->directory->getAbsolutePath('testDir/' . $fileName); + $driver = $this->directory->getDriver(); + $driver->filePutContents( + $modifiableFilePath, + file_get_contents(__DIR__ . '/../../_files/' . $fileName) ); $metadata = $this->metadataFactory->create([ 'title' => $title, @@ -97,7 +99,7 @@ public function testExecute( $this->assertEquals($description, $updatedMetadata->getDescription()); $this->assertEquals($keywords, $updatedMetadata->getKeywords()); - $this->driver->deleteFile($modifiableFilePath); + $driver->deleteFile($modifiableFilePath); } /** @@ -174,7 +176,7 @@ public function filesProvider(): array 'community' ], ], - [ + [ 'exiftool.gif', 'Updated Title', 'Updated Description', @@ -182,8 +184,8 @@ public function filesProvider(): array 'magento2', 'mediagallery' ] - ], - [ + ], + [ 'empty_exiftool.gif', 'Updated Title', 'Updated Description', @@ -191,7 +193,7 @@ public function filesProvider(): array 'magento2', 'mediagallery' ] - ] + ] ]; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php index ebe96183eb1f2..c1769f864e2ba 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php @@ -7,7 +7,10 @@ namespace Magento\MediaGalleryMetadata\Test\Integration\Model; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -22,12 +25,28 @@ class ExtractMetadataTest extends TestCase */ private $extractMetadata; + /** + * @var WriteInterface + */ + private $directory; + /** * @inheritdoc */ protected function setUp(): void { $this->extractMetadata = Bootstrap::getObjectManager()->get(ExtractMetadataInterface::class); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -46,7 +65,13 @@ public function testExecute( string $description, ?array $keywords ): void { - $path = realpath(__DIR__ . '/../../_files/' . $fileName); + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $driver = $this->directory->getDriver(); + $driver->filePutContents( + $path, + file_get_contents(__DIR__ . '/../../_files/' . $fileName) + ); + $metadata = $this->extractMetadata->execute($path); $this->assertEquals($title, $metadata->getTitle()); diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Gif/Segment/XmpTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Gif/Segment/XmpTest.php index 4bba73e3ca2a9..a9a00f607f2e6 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Gif/Segment/XmpTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Gif/Segment/XmpTest.php @@ -7,8 +7,9 @@ namespace Magento\MediaGalleryMetadata\Test\Integration\Model\Gif\Segment; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Filesystem; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; use Magento\MediaGalleryMetadata\Model\Gif\Segment\WriteXmp; @@ -31,11 +32,6 @@ class XmpTest extends TestCase */ private $xmpReader; - /** - * @var DriverInterface - */ - private $driver; - /** * @var ReadFile */ @@ -46,6 +42,11 @@ class XmpTest extends TestCase */ private $metadataFactory; + /** + * @var WriteInterface + */ + private $directory; + /** * @inheritdoc */ @@ -54,8 +55,18 @@ protected function setUp(): void $this->xmpWriter = Bootstrap::getObjectManager()->get(WriteXmp::class); $this->xmpReader = Bootstrap::getObjectManager()->get(ReadXmp::class); $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -74,7 +85,11 @@ public function testWriteReadGif( string $description, array $keywords ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $this->directory->getDriver()->filePutContents( + $path, + file_get_contents(__DIR__ . '/../../../../_files/' . $fileName) + ); $file = $this->fileReader->execute($path); $originalGifMetadata = $this->xmpReader->execute($file); diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/IptcTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/IptcTest.php index 932b71df28430..f79479a13dba1 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/IptcTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/IptcTest.php @@ -33,11 +33,6 @@ class IptcTest extends TestCase */ private $iptcReader; - /** - * @var DriverInterface - */ - private $driver; - /** * @var ReadFile */ @@ -51,20 +46,28 @@ class IptcTest extends TestCase /** * @var WriteInterface */ - private $varDirectory; + private $directory; /** * @inheritdoc */ protected function setUp(): void { - $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) - ->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); $this->iptcWriter = Bootstrap::getObjectManager()->get(WriteIptc::class); $this->iptcReader = Bootstrap::getObjectManager()->get(ReadIptc::class); $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -83,13 +86,12 @@ public function testWriteRead( string $description, array $keywords ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); - $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); - $this->driver->copy( + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $this->directory->getDriver()->filePutContents( $path, - $modifiableFilePath + file_get_contents(__DIR__ . '/../../../../_files/' . $fileName) ); - $modifiableFilePath = $this->fileReader->execute($modifiableFilePath); + $modifiableFilePath = $this->fileReader->execute($path); $originalMetadata = $this->iptcReader->execute($modifiableFilePath); $this->assertEmpty($originalMetadata->getTitle()); diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/XmpTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/XmpTest.php index 043e26f67853f..2fdb678a1b3fc 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/XmpTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/XmpTest.php @@ -7,7 +7,9 @@ namespace Magento\MediaGalleryMetadata\Test\Integration\Model\Jpeg\Segment; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -31,11 +33,6 @@ class XmpTest extends TestCase */ private $xmpReader; - /** - * @var DriverInterface - */ - private $driver; - /** * @var ReadFile */ @@ -46,6 +43,11 @@ class XmpTest extends TestCase */ private $metadataFactory; + /** + * @var WriteInterface + */ + private $directory; + /** * @inheritdoc */ @@ -54,8 +56,18 @@ protected function setUp(): void $this->xmpWriter = Bootstrap::getObjectManager()->get(WriteXmp::class); $this->xmpReader = Bootstrap::getObjectManager()->get(ReadXmp::class); $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -74,7 +86,11 @@ public function testWriteRead( string $description, array $keywords ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $this->directory->getDriver()->filePutContents( + $path, + file_get_contents(__DIR__ . '/../../../../_files/' . $fileName) + ); $file = $this->fileReader->execute($path); $originalMetadata = $this->xmpReader->execute($file); diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/IptcTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/IptcTest.php index d8bcfd7a94561..4fe8cebde630a 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/IptcTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/IptcTest.php @@ -51,20 +51,29 @@ class IptcTest extends TestCase /** * @var WriteInterface */ - private $varDirectory; + private $directory; /** * @inheritdoc */ protected function setUp(): void { - $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) - ->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); $this->iptcWriter = Bootstrap::getObjectManager()->get(WriteIptc::class); $this->iptcReader = Bootstrap::getObjectManager()->get(ReadIptc::class); $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->driver = $this->directory->getDriver(); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -83,13 +92,12 @@ public function testWriteRead( string $description, array $keywords ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); - $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); - $this->driver->copy( + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $this->driver->filePutContents( $path, - $modifiableFilePath + file_get_contents(__DIR__ . '/../../../../_files/' . $fileName) ); - $modifiableFilePath = $this->fileReader->execute($modifiableFilePath); + $modifiableFilePath = $this->fileReader->execute($path); $originalMetadata = $this->iptcReader->execute($modifiableFilePath); $this->assertEmpty($originalMetadata->getTitle()); diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/XmpTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/XmpTest.php index e3a3ab1709daa..5cd9c24a400c6 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/XmpTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Png/Segment/XmpTest.php @@ -34,11 +34,6 @@ class XmpTest extends TestCase */ private $xmpReader; - /** - * @var DriverInterface - */ - private $driver; - /** * @var ReadFile */ @@ -52,20 +47,28 @@ class XmpTest extends TestCase /** * @var WriteInterface */ - private $varDirectory; + private $directory; /** * @inheritdoc */ protected function setUp(): void { - $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) - ->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->directory = Bootstrap::getObjectManager()->get(FileSystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); $this->xmpWriter = Bootstrap::getObjectManager()->get(WriteXmp::class); $this->xmpReader = Bootstrap::getObjectManager()->get(ReadXmp::class); $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + $this->directory->create('testDir'); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->directory->delete('testDir'); } /** @@ -84,13 +87,12 @@ public function testWriteRead( string $description, array $keywords ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); - $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); - $this->driver->copy( + $path = $this->directory->getAbsolutePath('testDir/' . $fileName); + $this->directory->getDriver()->filePutContents( $path, - $modifiableFilePath + file_get_contents(__DIR__ . '/../../../../_files/' . $fileName) ); - $modifiableFilePath = $this->fileReader->execute($modifiableFilePath); + $modifiableFilePath = $this->fileReader->execute($path); $originalMetadata = $this->xmpReader->execute($modifiableFilePath); $this->assertEmpty($originalMetadata->getTitle()); diff --git a/app/code/Magento/MediaGalleryMetadata/composer.json b/app/code/Magento/MediaGalleryMetadata/composer.json index 0775ae3c44b30..867a99b3634ac 100644 --- a/app/code/Magento/MediaGalleryMetadata/composer.json +++ b/app/code/Magento/MediaGalleryMetadata/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-metadata", "description": "Magento module responsible for images metadata processing", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-metadata-api": "*" }, diff --git a/app/code/Magento/MediaGalleryMetadataApi/composer.json b/app/code/Magento/MediaGalleryMetadataApi/composer.json index 41968a5e8e217..e6138f388b9d1 100644 --- a/app/code/Magento/MediaGalleryMetadataApi/composer.json +++ b/app/code/Magento/MediaGalleryMetadataApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-metadata-api", "description": "Magento module responsible for media gallery metadata implementation API", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGalleryRenditions/composer.json b/app/code/Magento/MediaGalleryRenditions/composer.json index 3ae993878161e..6df335eb490cf 100644 --- a/app/code/Magento/MediaGalleryRenditions/composer.json +++ b/app/code/Magento/MediaGalleryRenditions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-renditions", "description": "Magento module that implements height and width fields for for media gallery items.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-renditions-api": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryRenditionsApi/composer.json b/app/code/Magento/MediaGalleryRenditionsApi/composer.json index 46bf2fb3ad02e..7c24a9a04163b 100644 --- a/app/code/Magento/MediaGalleryRenditionsApi/composer.json +++ b/app/code/Magento/MediaGalleryRenditionsApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-renditions-api", "description": "Magento module that is responsible for the API implementation of Media Gallery Renditions.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 96a74db83b8c8..3d3db8bdf18db 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -82,7 +82,7 @@ public function execute(string $path): AssetInterface * SPL file info is not compatible with remote storages and must not be used. */ $file = $this->getFileInfo->execute($absolutePath); - [$width, $height] = getimagesizefromstring($absolutePath); + [$width, $height] = getimagesizefromstring($driver->fileGetContents($absolutePath)); $meta = [ 'size' => $file->getSize(), 'extension' => $file->getExtension(), diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php index 8a44307298065..9a895435bb2bb 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php @@ -48,11 +48,11 @@ class SynchronizeFilesTest extends TestCase */ protected function setUp(): void { - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); + $this->driver = $this->mediaDirectory->getDriver(); } /** @@ -72,9 +72,9 @@ public function testExecute( ): void { $path = realpath(__DIR__ . '/../_files/' . $file); $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); - $this->driver->copy( - $path, - $modifiableFilePath + $this->driver->filePutContents( + $modifiableFilePath, + file_get_contents($path) ); $this->synchronizeFiles->execute([$file]); @@ -83,7 +83,6 @@ public function testExecute( $this->assertEquals($title, $loadedAsset->getTitle()); $this->assertEquals($source, $loadedAsset->getSource()); - $this->driver->deleteFile($modifiableFilePath); } diff --git a/app/code/Magento/MediaGallerySynchronization/composer.json b/app/code/Magento/MediaGallerySynchronization/composer.json index 200d3cf440e07..db309b357347b 100644 --- a/app/code/Magento/MediaGallerySynchronization/composer.json +++ b/app/code/Magento/MediaGallerySynchronization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization", "description": "Magento module provides implementation of the media gallery data synchronization.", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaGallerySynchronizationApi/composer.json b/app/code/Magento/MediaGallerySynchronizationApi/composer.json index 597d6b90c0bd3..05bdcf2fd280c 100644 --- a/app/code/Magento/MediaGallerySynchronizationApi/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization-api", "description": "Magento module responsible for the media gallery synchronization implementation API", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-api": "*" }, diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json index d17102fa2d01c..8d11e85145e65 100644 --- a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization-metadata", "description": "Magento module responsible for images metadata synchronization", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-metadata-api": "*", diff --git a/app/code/Magento/MediaGalleryUi/composer.json b/app/code/Magento/MediaGalleryUi/composer.json index 4617ab50eddbe..15073756638bf 100644 --- a/app/code/Magento/MediaGalleryUi/composer.json +++ b/app/code/Magento/MediaGalleryUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-ui", "description": "Magento module responsible for the media gallery UI implementation", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-ui": "*", diff --git a/app/code/Magento/MediaGalleryUiApi/composer.json b/app/code/Magento/MediaGalleryUiApi/composer.json index cf581de06f920..dd331cefe65a8 100644 --- a/app/code/Magento/MediaGalleryUiApi/composer.json +++ b/app/code/Magento/MediaGalleryUiApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-ui-api", "description": "Magento module responsible for the media gallery UI implementation API", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/MediaStorage/App/Media.php b/app/code/Magento/MediaStorage/App/Media.php index 1642b928d5d48..b98dc8ef41e05 100644 --- a/app/code/Magento/MediaStorage/App/Media.php +++ b/app/code/Magento/MediaStorage/App/Media.php @@ -1,7 +1,5 @@ <?php /** - * Media application - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -45,8 +43,6 @@ class Media implements AppInterface private $isAllowed; /** - * Media directory path - * * @var string */ private $mediaDirectoryPath; @@ -238,7 +234,10 @@ private function createLocalCopy(): void */ private function checkMediaDirectoryChanged(): bool { - return rtrim($this->mediaDirectoryPath, '/') !== rtrim($this->directoryMedia->getAbsolutePath(), '/'); + $mediaDirectoryPath = $this->mediaDirectoryPath ? rtrim($this->mediaDirectoryPath, '/') : ''; + $directoryMediaAbsolutePath = $this->directoryMedia->getAbsolutePath(); + $directoryMediaAbsolutePath = $directoryMediaAbsolutePath ? rtrim($directoryMediaAbsolutePath, '/') : ''; + return $mediaDirectoryPath !== $directoryMediaAbsolutePath; } /** diff --git a/app/code/Magento/MediaStorage/composer.json b/app/code/Magento/MediaStorage/composer.json index 384fd6b41edeb..d2116ce8a5b16 100644 --- a/app/code/Magento/MediaStorage/composer.json +++ b/app/code/Magento/MediaStorage/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php b/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php index 01ad8578aab54..3f99d7667310e 100644 --- a/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php +++ b/app/code/Magento/MessageQueue/Test/Unit/Console/StartConsumerCommandTest.php @@ -102,12 +102,12 @@ protected function setUp(): void */ public function testExecute( ?string $pidFilePath, - bool $singleThread, - ?int $multiProcess, - int $lockExpects, - bool $isLocked, - int $unlockExpects, - int $runProcessExpects, + bool $singleThread, + ?int $multiProcess, + int $lockExpects, + bool $isLocked, + int $unlockExpects, + int $runProcessExpects, int $expectedReturn ): void { $areaCode = 'area_code'; diff --git a/app/code/Magento/MessageQueue/composer.json b/app/code/Magento/MessageQueue/composer.json index efc166c268261..0d7557d8d107f 100644 --- a/app/code/Magento/MessageQueue/composer.json +++ b/app/code/Magento/MessageQueue/composer.json @@ -8,7 +8,7 @@ "magento/framework": "*", "magento/framework-message-queue": "*", "magento/magento-composer-installer": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json index 38a61214ea682..0aa6da768cba8 100644 --- a/app/code/Magento/Msrp/composer.json +++ b/app/code/Magento/Msrp/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-downloadable": "*", diff --git a/app/code/Magento/MsrpConfigurableProduct/composer.json b/app/code/Magento/MsrpConfigurableProduct/composer.json index 176022c984a84..c0fbba24831f1 100644 --- a/app/code/Magento/MsrpConfigurableProduct/composer.json +++ b/app/code/Magento/MsrpConfigurableProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-msrp": "*", diff --git a/app/code/Magento/MsrpGroupedProduct/composer.json b/app/code/Magento/MsrpGroupedProduct/composer.json index a81213e95d251..1a5c1767a0edd 100644 --- a/app/code/Magento/MsrpGroupedProduct/composer.json +++ b/app/code/Magento/MsrpGroupedProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-msrp": "*", diff --git a/app/code/Magento/Multishipping/composer.json b/app/code/Magento/Multishipping/composer.json index e18dd5e97edf2..97dcb989c91dc 100644 --- a/app/code/Magento/Multishipping/composer.json +++ b/app/code/Magento/Multishipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/MysqlMq/composer.json b/app/code/Magento/MysqlMq/composer.json index ece1f49d9f215..8419e09490b26 100644 --- a/app/code/Magento/MysqlMq/composer.json +++ b/app/code/Magento/MysqlMq/composer.json @@ -9,7 +9,7 @@ "magento/framework-message-queue": "*", "magento/magento-composer-installer": "*", "magento/module-store": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/NewRelicReporting/composer.json b/app/code/Magento/NewRelicReporting/composer.json index d95fecac19a15..d4b66fbf907e5 100644 --- a/app/code/Magento/NewRelicReporting/composer.json +++ b/app/code/Magento/NewRelicReporting/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php b/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php index 236101745b98e..2ea4c2f81cd81 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php @@ -21,8 +21,6 @@ class Edit extends Widget { /** - * Core registry - * * @var \Magento\Framework\Registry */ protected $_coreRegistry = null; @@ -216,7 +214,9 @@ public function getForm() */ public function getJsTemplateName() { - return addcslashes($this->getModel()->getTemplateCode(), "\"\r\n\\"); + $templateCode = $this->getModel()->getTemplateCode(); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return $templateCode ? addcslashes($templateCode, "\"\r\n\\") : ''; } /** diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminCreateNewsletterTemplateActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminCreateNewsletterTemplateActionGroup.xml index b7750f274abb3..cc691aae36a5f 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminCreateNewsletterTemplateActionGroup.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminCreateNewsletterTemplateActionGroup.xml @@ -15,7 +15,8 @@ Clicks the Show/Hide button for the Template Content field before text is sent to this field. </description> </annotations> - - <click selector="{{BasicFieldNewsletterSection.showHide}}" stepKey="showWYSIWYG" before="fillTemplateContentField"/> + + <waitForElementVisible selector="{{BasicFieldNewsletterSection.showHide}}" stepKey="waitForButton" before="showWYSIWYG"/> + <conditionalClick selector="{{BasicFieldNewsletterSection.showHide}}" dependentSelector="{{TinyMCESection.ContentEditor}}" visible="false" stepKey="showWYSIWYG" before="waitForTemplateContentField"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingCreateNewsletterTemplateActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingCreateNewsletterTemplateActionGroup.xml index 7d1debfc919ba..b5965c016a93b 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingCreateNewsletterTemplateActionGroup.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingCreateNewsletterTemplateActionGroup.xml @@ -22,6 +22,7 @@ <fillField stepKey="fillTemplateSubjectField" selector="{{BasicFieldNewsletterSection.templateSubject}}" userInput="{{subject}}"/> <fillField stepKey="fillSenderNameField" selector="{{BasicFieldNewsletterSection.senderName}}" userInput="{{senderName}}"/> <fillField stepKey="fillSenderEmailField" selector="{{BasicFieldNewsletterSection.senderEmail}}" userInput="{{senderEmail}}"/> + <waitForElementVisible stepKey="waitForTemplateContentField" selector="{{TinyMCESection.Content}}"/> <fillField stepKey="fillTemplateContentField" selector="{{TinyMCESection.Content}}" userInput="{{templateContent}}"/> <!--Saving Created Template--> <click stepKey="clickSaveTemplateButton" selector="{{AdminNewsletterMainActionsSection.saveTemplateButton}}"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml index b78c42fbbe95d..7850d48b6cab2 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml @@ -48,6 +48,10 @@ <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{CustomerEntityOne.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <comment userInput="Adding the comment to replace CliIndexerReindexActionGroup action group ('indexer:reindex' commands) for preserving Backward Compatibility" stepKey="reindex"/> <comment userInput="Adding the comment to replace CliCacheFlushActionGroup action group ('cache:flush' command) for preserving Backward Compatibility" stepKey="flushCache"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> @@ -58,8 +62,9 @@ <argument name="Customer" value="CustomerEntityOne"/> </actionGroup> <!--Sign Out--> - <amOnPage url="{{StorefrontCustomerLogoutPage.url}}" stepKey="customerOnLogoutPage"/> - <waitForPageLoad stepKey="waitLogoutCustomer"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logout"/> + <comment userInput="BIC workaround" stepKey="customerOnLogoutPage"/> + <comment userInput="BIC workaround" stepKey="waitLogoutCustomer"/> <!--Create new Account with the same email address. (unchecked Sign Up for Newsletter checkbox)--> <actionGroup ref="StorefrontCreateNewAccountNewsletterUncheckedActionGroup" stepKey="createNewAccountNewsletterUnchecked"> <argument name="Customer" value="CustomerEntityOne"/> diff --git a/app/code/Magento/Newsletter/composer.json b/app/code/Magento/Newsletter/composer.json index 13a08b8ae40e4..43f9889bec55c 100644 --- a/app/code/Magento/Newsletter/composer.json +++ b/app/code/Magento/Newsletter/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/NewsletterGraphQl/composer.json b/app/code/Magento/NewsletterGraphQl/composer.json index 13c90d61525b4..41db529f4f376 100644 --- a/app/code/Magento/NewsletterGraphQl/composer.json +++ b/app/code/Magento/NewsletterGraphQl/composer.json @@ -6,7 +6,7 @@ }, "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-customer": "*", "magento/module-newsletter": "*", diff --git a/app/code/Magento/OfflinePayments/Model/Banktransfer.php b/app/code/Magento/OfflinePayments/Model/Banktransfer.php index cf3e573516919..b4d04eeaf94f8 100644 --- a/app/code/Magento/OfflinePayments/Model/Banktransfer.php +++ b/app/code/Magento/OfflinePayments/Model/Banktransfer.php @@ -15,7 +15,7 @@ */ class Banktransfer extends \Magento\Payment\Model\Method\AbstractMethod { - const PAYMENT_METHOD_BANKTRANSFER_CODE = 'banktransfer'; + public const PAYMENT_METHOD_BANKTRANSFER_CODE = 'banktransfer'; /** * Payment method code @@ -52,6 +52,7 @@ class Banktransfer extends \Magento\Payment\Model\Method\AbstractMethod */ public function getInstructions() { - return trim($this->getConfigData('instructions')); + $instructions = $this->getConfigData('instructions'); + return $instructions !== null ? trim($instructions) : ''; } } diff --git a/app/code/Magento/OfflinePayments/Model/Cashondelivery.php b/app/code/Magento/OfflinePayments/Model/Cashondelivery.php index 9c408efdd00df..f49287a6627a2 100644 --- a/app/code/Magento/OfflinePayments/Model/Cashondelivery.php +++ b/app/code/Magento/OfflinePayments/Model/Cashondelivery.php @@ -15,7 +15,7 @@ */ class Cashondelivery extends \Magento\Payment\Model\Method\AbstractMethod { - const PAYMENT_METHOD_CASHONDELIVERY_CODE = 'cashondelivery'; + public const PAYMENT_METHOD_CASHONDELIVERY_CODE = 'cashondelivery'; /** * Payment method code @@ -52,6 +52,7 @@ class Cashondelivery extends \Magento\Payment\Model\Method\AbstractMethod */ public function getInstructions() { - return trim($this->getConfigData('instructions')); + $instructions = $this->getConfigData('instructions'); + return $instructions !== null ? trim($instructions) : ''; } } diff --git a/app/code/Magento/OfflinePayments/composer.json b/app/code/Magento/OfflinePayments/composer.json index 957a029114f42..17cba3fcb68ff 100644 --- a/app/code/Magento/OfflinePayments/composer.json +++ b/app/code/Magento/OfflinePayments/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-payment": "*", diff --git a/app/code/Magento/OfflineShipping/composer.json b/app/code/Magento/OfflineShipping/composer.json index ff7ddb904deb9..01c1e443568ed 100644 --- a/app/code/Magento/OfflineShipping/composer.json +++ b/app/code/Magento/OfflineShipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/PageCache/composer.json b/app/code/Magento/PageCache/composer.json index c518fd5a9b6e7..fcccedae9faa7 100644 --- a/app/code/Magento/PageCache/composer.json +++ b/app/code/Magento/PageCache/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Payment/Model/Method/Cc.php b/app/code/Magento/Payment/Model/Method/Cc.php index 11629308cd46b..3817b2648106f 100644 --- a/app/code/Magento/Payment/Model/Method/Cc.php +++ b/app/code/Magento/Payment/Model/Method/Cc.php @@ -286,7 +286,8 @@ public function assignData(\Magento\Framework\DataObject $data) [ 'cc_type' => $additionalData->getCcType(), 'cc_owner' => $additionalData->getCcOwner(), - 'cc_last_4' => substr($additionalData->getCcNumber(), -4), + 'cc_last_4' => $additionalData->getCcNumber() !== null ? + substr($additionalData->getCcNumber(), -4) : '', 'cc_number' => $additionalData->getCcNumber(), 'cc_cid' => $additionalData->getCcCid(), 'cc_exp_month' => $additionalData->getCcExpMonth(), @@ -323,8 +324,9 @@ public function validateCcNum($ccNumber) { $cardNumber = strrev($ccNumber); $numSum = 0; + $length = strlen($cardNumber); - for ($i = 0; $i < strlen($cardNumber); $i++) { + for ($i = 0; $i < $length; $i++) { $currentNum = substr($cardNumber, $i, 1); /** diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json index e864327c45808..8aa0a80aa5448 100644 --- a/app/code/Magento/Payment/composer.json +++ b/app/code/Magento/Payment/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-config": "*", diff --git a/app/code/Magento/PaymentGraphQl/composer.json b/app/code/Magento/PaymentGraphQl/composer.json index f410293ab1136..43ff1033966af 100644 --- a/app/code/Magento/PaymentGraphQl/composer.json +++ b/app/code/Magento/PaymentGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/PayLaterLink.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/PayLaterLink.php new file mode 100644 index 0000000000000..d4f7fd31481dd --- /dev/null +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/PayLaterLink.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Paypal\Block\Adminhtml\System\Config; + +use Magento\Backend\Block\Template\Context; +use Magento\Config\Block\System\Config\Form\Field; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Store\Model\ScopeInterface; + +class PayLaterLink extends Field +{ + /** + * Location of the PayPal's "Merchant Country" config param + */ + private const XML_PATH_PAYPAL_MERCHANT_COUNTRY = 'paypal/general/merchant_country'; + + /** + * Default country is set as US. + */ + private const DEFAULT_COUNTRY = 'US'; + + /** + * @var array. + */ + private const ARRAY_PAYLATER_SUPPORTED_COUNTRIES = ['US','GB','DE','FR','AU']; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param Context $context + * @param ScopeConfigInterface $scopeConfig + * @param array $data + */ + public function __construct( + Context $context, + ScopeConfigInterface $scopeConfig, + array $data = [] + ) { + parent::__construct($context, $data); + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function _getElementHtml(AbstractElement $element): string + { + $html = parent::_getElementHtml($element); + $html .= $this->getPayLaterCommentHtml(); + $country = $this->getPayLaterCountry(); + + return (in_array($country, self::ARRAY_PAYLATER_SUPPORTED_COUNTRIES)) ? + sprintf($html, strtolower($country)) : sprintf($html, strtolower(self::DEFAULT_COUNTRY)); + } + + /** + * Get pay later merchant country + * + * @return string + */ + private function getPayLaterCountry(): string + { + return $this->getRequest()->getParam('paypal_country') ?: ($this->scopeConfig->getValue( + self::XML_PATH_PAYPAL_MERCHANT_COUNTRY, + ScopeInterface::SCOPE_STORES + ) ?: self::DEFAULT_COUNTRY); + } + + /** + * Get pay later comment html + * + * @return string + */ + private function getPayLaterCommentHtml(): string + { + return '<p class="note"> + Displays Pay Later messaging for available offers. Restrictions apply. Click + <a href="https://developer.paypal.com/docs/business/pay-later/%s/commerce-platforms/magento2/magento-paypal/" + target="_blank">here</a> to learn more</p>'; + } +} diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutDisableBillingAgreementActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutDisableBillingAgreementActionGroup.xml new file mode 100644 index 0000000000000..cecb7ecaa5fb0 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutDisableBillingAgreementActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalExpressCheckoutDisableBillingAgreementActionGroup"> + <annotations> + <description>Goes to the 'Configuration' page for 'Payment Methods'. Disable billing agreement option.</description> + </annotations> + <arguments> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <click selector="{{PayPalExpressCheckoutConfigSection.advancePaypalSettings(countryCode)}}" stepKey="expandPaypalAdvancedSettings"/> + <click selector="{{PayPalExpressCheckoutConfigSection.paypalBillingAgreement(countryCode)}}" stepKey="expandBillingAgreementSettings"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.billingDisable(countryCode)}}" userInput="No" stepKey="disableBillingAgreement"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig3"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutEnableBillingAgreementActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutEnableBillingAgreementActionGroup.xml new file mode 100644 index 0000000000000..4e579dff50a6e --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutEnableBillingAgreementActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalExpressCheckoutEnableBillingAgreementActionGroup"> + <annotations> + <description>Goes to the 'Configuration' page for 'Payment Methods'. Enable billing agreement option.</description> + </annotations> + <arguments> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <click selector="{{PayPalExpressCheckoutConfigSection.advancePaypalSettings(countryCode)}}" stepKey="expandPaypalAdvancedSettings"/> + <click selector="{{PayPalExpressCheckoutConfigSection.paypalBillingAgreement(countryCode)}}" stepKey="expandBillingAgreementSettings"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.billingDisable(countryCode)}}" userInput="Yes" stepKey="disableBillingAgreement"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterDisableActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterDisableActionGroup.xml new file mode 100644 index 0000000000000..40137ad2a2421 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterDisableActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalExpressCheckoutPayLaterDisableActionGroup"> + <annotations> + <description>Goes to the 'Configuration' page for 'Payment Methods'. Disable Pay later option.</description> + </annotations> + <arguments> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.enablePayLater(countryCode)}}" userInput="No" stepKey="disablePayLater"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig5"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterEnableActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterEnableActionGroup.xml new file mode 100644 index 0000000000000..e490e220e6a8a --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalExpressCheckoutPayLaterEnableActionGroup.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalExpressCheckoutPayLaterEnableActionGroup"> + <annotations> + <description>Goes to the 'Configuration' page for 'Payment Methods'. Enable pay later option.</description> + </annotations> + <arguments> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.enablePayLater(countryCode)}}" userInput="Yes" stepKey="enablePayLater"/> + <click selector="{{PayPalExpressCheckoutConfigSection.payLaterConfigDropDown(countryCode)}}" stepKey="clickPayLaterConfiguration" /> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.enablePayLaterIn(countryCode)}}" userInput="Yes" stepKey="enablePayLaterIn"/> + <click selector="{{PayPalExpressCheckoutConfigSection.payLaterCatalogPageConfigDropDown(countryCode)}}" stepKey="clickCatalogPageConfig" /> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.display(countryCode)}}" userInput="Yes" stepKey="enableDisplay"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.position(countryCode)}}" userInput="near_pp_button" stepKey="enablePayLaterButton"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkActionGroup.xml new file mode 100644 index 0000000000000..e4a5ec933d939 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalRegionalLinkActionGroup"> + <annotations> + <description>Expands the 'PAYPAL EXPRESS CHECKOUT PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code.</description> + </annotations> + <arguments> + <argument name="payPalConfigType"/> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <click selector="{{payPalConfigType.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <grabAttributeFrom userInput="href" selector="{{PayPalRegionalCommentSection.country(countryCode)}}" stepKey="grabCommentText"/> + <assertStringContainsString stepKey="verifyCommentLink"> + <expectedResult type="string">https://developer.paypal.com/docs/business/pay-later/{{countryCode}}/commerce-platforms/magento2/magento-paypal/</expectedResult> + <actualResult type="string">$grabCommentText</actualResult> + </assertStringContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkOtherActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkOtherActionGroup.xml new file mode 100644 index 0000000000000..21fa26baecee8 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AdminPayPalRegionalLinkOtherActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPayPalRegionalLinkOtherActionGroup"> + <annotations> + <description>Expands the 'PAYPAL EXPRESS CHECKOUT PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code.</description> + </annotations> + <arguments> + <argument name="payPalConfigType"/> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <click selector="{{payPalConfigType.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <grabAttributeFrom userInput="href" selector="{{PayPalRegionalCommentSection.other(countryCode)}}" stepKey="grabCommentText"/> + <assertStringContainsString stepKey="verifyCommentLink"> + <expectedResult type="string">https://developer.paypal.com/docs/business/pay-later/{{countryCode}}/commerce-platforms/magento2/magento-paypal/</expectedResult> + <actualResult type="string">$grabCommentText</actualResult> + </assertStringContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml index 01ec295d8bf63..bff85ddcca5d7 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml @@ -28,6 +28,7 @@ <fillField selector ="{{PayPalExpressCheckoutConfigSection.signature(countryCode)}}" userInput="{{credentials.magento/paypal_express_checkout_us_api_signature}}" stepKey="inputAPISignature"/> <selectOption selector ="{{PayPalExpressCheckoutConfigSection.sandboxMode(countryCode)}}" userInput="Yes" stepKey="enableSandboxMode"/> <selectOption selector="{{PayPalExpressCheckoutConfigSection.enableSolution(countryCode)}}" userInput="Yes" stepKey="enableSolution"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.enableInContext(countryCode)}}" userInput="Yes" stepKey="enableInContext"/> <waitForElementVisible selector="{{PayPalExpressCheckoutConfigSection.merchantID(countryCode)}}" stepKey="waitForMerchantIdField"/> <fillField selector ="{{PayPalExpressCheckoutConfigSection.merchantID(countryCode)}}" userInput="{{credentials.magento/paypal_express_checkout_us_merchant_id}}" stepKey="inputMerchantID"/> <!--Save configuration--> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalExpressCheckoutConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalExpressCheckoutConfigSection.xml index 180ca4f744c1d..a8cbd73c21aa8 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalExpressCheckoutConfigSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalExpressCheckoutConfigSection.xml @@ -16,6 +16,16 @@ <element name="signature" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_express_checkout_required_express_checkout_api_signature" parameterized="true"/> <element name="sandboxMode" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_express_checkout_required_express_checkout_sandbox_flag" parameterized="true"/> <element name="enableSolution" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_enable_express_checkout" parameterized="true"/> + <element name="enableInContext" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_enable_in_context_checkout" parameterized="true"/> <element name="merchantID" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_merchant_id" parameterized="true"/> + <element name="enablePayLater" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_enable_paypal_paylater_experience" parameterized="true"/> + <element name="payLaterConfigDropDown" type="button" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_advertise_paylater-head" parameterized="true"/> + <element name="enablePayLaterIn" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_advertise_paylater_paylater_enabled" parameterized="true" /> + <element name="payLaterCatalogPageConfigDropDown" type="button" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_advertise_paylater_settings_paylater_productpage-head" parameterized="true" /> + <element name="display" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_advertise_paylater_settings_paylater_productpage_paylater_productpage_display" parameterized="true"/> + <element name="position" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_advertise_paylater_settings_paylater_productpage_paylater_productpage_position" parameterized="true"/> + <element name="advancePaypalSettings" type="button" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_settings_ec_settings_ec_advanced-head" parameterized="true" /> + <element name="paypalBillingAgreement" type="button" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_settings_ec_settings_ec_advanced_express_checkout_billing_agreement-head" parameterized="true" /> + <element name="billingDisable" type="input" selector="#payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_settings_ec_settings_ec_advanced_express_checkout_billing_agreement_active" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalRegionalCommentSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalRegionalCommentSection.xml new file mode 100644 index 0000000000000..764d3d017c7de --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalRegionalCommentSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="PayPalRegionalCommentSection"> + <element name="country" type="text" selector="//tr[@id='row_payment_{{countryCode}}_paypal_alternative_payment_methods_express_checkout_{{countryCode}}_express_checkout_required_enable_paypal_paylater_experience']//td //p[@class='note'] //a[text()='here']" parameterized="true"/> + <element name="other" type="text" selector="//tr[@id='row_payment_{{countryCode}}_express_checkout_other_express_checkout_required_enable_paypal_paylater_experience']//td //p[@class='note'] //a[text()='here']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkForPayLaterTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkForPayLaterTest.xml new file mode 100644 index 0000000000000..071d991427ba9 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkForPayLaterTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRegionalLinkForPayLaterTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayLater Comment Regional Link"/> + <title value="Check Regional Comment Link For PayPal PayLater"/> + <description value="Default link country for Paypal Pay Later and for all unsupported countries is US"/> + <severity value="AVERAGE"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <selectOption selector="{{PaymentsConfigSection.merchantCountry}}" userInput="United Kingdom" stepKey="setMerchantCountry"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminPayPalRegionalLinkActionGroup" stepKey="verifyPayPalCommentCommentLink"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="gb"/> + </actionGroup> + <selectOption selector="{{PaymentsConfigSection.merchantCountry}}" userInput="France" stepKey="setMerchantCountry2"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="AdminPayPalRegionalLinkOtherActionGroup" stepKey="verifyPayPalCommentCommentLink2"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutOtherCountryConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="fr"/> + </actionGroup> + <selectOption selector="{{PaymentsConfigSection.merchantCountry}}" userInput="United States" stepKey="setMerchantCountry3"/> + <waitForPageLoad stepKey="waitForPageLoa3"/> + <actionGroup ref="AdminPayPalRegionalLinkActionGroup" stepKey="verifyPayPalCommentCommentLink3"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="us"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkWithMerchantCountryFromConfigTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkWithMerchantCountryFromConfigTest.xml new file mode 100644 index 0000000000000..124cfa17558ed --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminRegionalLinkWithMerchantCountryFromConfigTest.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRegionalLinkWithMerchantCountryFromConfigTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayLater Comment Regional Link With Merchant Country From Config"/> + <title value="Check Comment Regional Link With Merchant Country From Config For PayPal PayLater"/> + <description value="Default link country for Paypal Pay Later and for all unsupported countries is US"/> + <severity value="AVERAGE"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <magentoCLI command="config:set {{MerchantFrance.path}} {{MerchantFrance.value}}" stepKey="setMerchantCountryDefault"/> + </before> + <after> + <magentoCLI command="config:set paypal/general/merchant_country US" stepKey="setMerchantCountry4"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminPayPalRegionalLinkOtherActionGroup" stepKey="verifyPayPalCommentCommentLink"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutOtherCountryConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="fr"/> + </actionGroup> + <selectOption selector="{{PaymentsConfigSection.merchantCountry}}" userInput="United Kingdom" stepKey="setMerchantCountry2"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="AdminPayPalRegionalLinkActionGroup" stepKey="verifyPayPalCommentCommentLink2"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="gb"/> + </actionGroup> + <selectOption selector="{{PaymentsConfigSection.merchantCountry}}" userInput="United States" stepKey="setMerchantCountry3"/> + <waitForPageLoad stepKey="waitForPageLoa3"/> + <actionGroup ref="AdminPayPalRegionalLinkActionGroup" stepKey="verifyPayPalCommentCommentLink3"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> + <argument name="enabledOption" value="Yes"/> + <argument name="countryCode" value="us"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml index b910ed4cfe6e6..62720f2f3ae09 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml @@ -111,9 +111,11 @@ <!-- I see order successful Page instead of Order Review Page --> <actionGroup ref="AssertStorefrontCheckoutSuccessActionGroup" stepKey="assertCheckoutSuccess"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="getOrderNumber"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="addFilterToGridAndOpenOrder"> - <argument name="orderId" value="{$getOrderNumber}"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="getOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="addFilterToGridAndOpenOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <actionGroup ref="AdminAssertNoAuthorizeButtonOnOrderPageActionGroup" stepKey="dontSeeOrderWaitingForAuthorize"/> <actionGroup ref="AdminOrderViewCheckStatusActionGroup" stepKey="checkOrderStatus"> diff --git a/app/code/Magento/Paypal/composer.json b/app/code/Magento/Paypal/composer.json index 753eba2297724..5f01874c120a5 100644 --- a/app/code/Magento/Paypal/composer.json +++ b/app/code/Magento/Paypal/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-authorization": "*", diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index 920701a6d05b5..62df44be46006 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -155,15 +155,8 @@ <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - <group id="advertise_paylater"> - <field id="paylater_enabled" translate="label comment"> - <comment> - <![CDATA[Display pay later messaging on your site for offers like Pay in 3, - which lets customers pay with 3 interest- free monthly payments. We’ll show messages - on your site to promote this feature for you. You may not promote pay later offers - with any other content, marketing, or materials.]]> - </comment> - </field> + <group id="advertise_paylater" showInDefault="1" showInWebsite="1"> + <field id="paylater_enabled" showInDefault="1" showInWebsite="1"/> </group> </group> </group> @@ -380,20 +373,7 @@ <group id="express_checkout_required"> <field id="enable_paypal_paylater_experience" showInDefault="1" showInWebsite="1"/> <group id="advertise_paylater" showInDefault="1" showInWebsite="1"> - <field id="paylater_enabled"> - <comment> - <![CDATA[Affichez le Paiement en 4X PayPal sur votre site. Le Paiement en 4X PayPal - permet aux consommateurs français de payer en 4 versements égaux. Vous pouvez - promouvoir le Paiement en 4X PayPal uniquement si vous êtes un commerçant basé en - France, avec un site internet en français et une intégration PayPal standard. Les - marchands ayant l’outil Vaulting (coffre-fort numérique) ou une intégration de - paiements récurrents/abonnement, ainsi que ceux présentant certaines activités - (vente de biens numériques / de biens non physiques) ne sont pas éligibles pour - promouvoir le Paiement en 4X PayPal. Nous afficherons des messages sur votre site - pour promouvoir le Paiement en 4X PayPal. Vous ne pouvez pas promouvoir le Paiement - en 4X PayPal avec un autre contenu, quel qu’il soit.]]> - </comment> - </field> + <field id="paylater_enabled" showInDefault="1" showInWebsite="1"/> </group> </group> </group> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 3db823e5fe722..c262c9ed6a314 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -194,14 +194,9 @@ </field> <field id="enable_paypal_paylater_experience" translate="label comment" type="select" sortOrder="27" showInDefault="1" showInWebsite="1"> <label>Enable PayPal Pay Later Experience</label> - <comment> - <![CDATA[Recommended. <strong>Advertise PayPal Credit</strong> is deprecated. - See PayPal Pay Later details and list of supported regions - <a href="https://developer.paypal.com/docs/business/pay-later/us/#eligibility" target="_blank"> - here</a>.]]> - </comment> <config_path>payment/paypal_paylater/experience_active</config_path> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\PayLaterLink</frontend_model> <attribute type="shared">1</attribute> </field> <group id="advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="30"> diff --git a/app/code/Magento/Paypal/view/frontend/web/js/order-review.js b/app/code/Magento/Paypal/view/frontend/web/js/order-review.js index e7820321d8cdc..55116694e52be 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/order-review.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/order-review.js @@ -143,14 +143,14 @@ define([ success: function (response) { var msg; - if ($.type(response) === 'object' && !$.isEmptyObject(response)) { + if (typeof response === 'object' && !$.isEmptyObject(response)) { if (response['error_messages']) { this._ajaxComplete(); msg = response['error_messages']; /* eslint-disable max-depth */ if (msg) { - if ($.type(msg) === 'array') { + if (Array.isArray(msg)) { msg = msg.join('\n'); } } @@ -216,7 +216,7 @@ define([ _updateOrderSubmit: function (shouldDisable, fn) { this._toggleButton(this.options.orderReviewSubmitSelector, shouldDisable); - if ($.type(fn) === 'function') { + if (typeof fn === 'function') { fn.call(this); } }, diff --git a/app/code/Magento/PaypalCaptcha/composer.json b/app/code/Magento/PaypalCaptcha/composer.json index 9010f15764633..f76f7c860ee46 100644 --- a/app/code/Magento/PaypalCaptcha/composer.json +++ b/app/code/Magento/PaypalCaptcha/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-captcha": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/PaypalGraphQl/composer.json b/app/code/Magento/PaypalGraphQl/composer.json index 19c2c958b27ab..1c0d53d15ec90 100644 --- a/app/code/Magento/PaypalGraphQl/composer.json +++ b/app/code/Magento/PaypalGraphQl/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/Persistent/composer.json b/app/code/Magento/Persistent/composer.json index a8787f20d080c..1ab666647fa94 100644 --- a/app/code/Magento/Persistent/composer.json +++ b/app/code/Magento/Persistent/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index 22b397593b5be..be37ca0697363 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index 50da01718d13c..3ac99920c6a2a 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index 6256368d15aa8..bfb42a7521015 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -269,7 +269,7 @@ define([ i; if (isJSON) { - inputData = $.parseJSON(inputData); + inputData = JSON.parse(inputData); } for (i = 0; i < inputData.length; i++) { diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js index 63aa3e2a77a48..a37af596cc631 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js @@ -130,13 +130,6 @@ define([ return this._player.playing(); }, - /** - * Destroyer - */ - destroy: function () { - this._player.destroy(); - }, - /** * Calculates ratio for responsive videos * @private @@ -300,9 +293,8 @@ define([ * stops and unloads player * @private */ - destroy: function () { + _destroy: function () { this.stop(); - this._player.destroy(); } }); diff --git a/app/code/Magento/Quote/composer.json b/app/code/Magento/Quote/composer.json index 1b47f993dfb64..24a722746833a 100644 --- a/app/code/Magento/Quote/composer.json +++ b/app/code/Magento/Quote/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json index b5182d45f5495..ed894d2ce1a63 100644 --- a/app/code/Magento/QuoteAnalytics/composer.json +++ b/app/code/Magento/QuoteAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/QuoteBundleOptions/composer.json b/app/code/Magento/QuoteBundleOptions/composer.json index 2c4b9a2c8cb92..32bfdcf6ef545 100644 --- a/app/code/Magento/QuoteBundleOptions/composer.json +++ b/app/code/Magento/QuoteBundleOptions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-bundle-options", "description": "Magento module provides data provider for creating buy request for bundle products", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteConfigurableOptions/composer.json b/app/code/Magento/QuoteConfigurableOptions/composer.json index b7490dbde012c..5cb3367bc9573 100644 --- a/app/code/Magento/QuoteConfigurableOptions/composer.json +++ b/app/code/Magento/QuoteConfigurableOptions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-configurable-options", "description": "Magento module provides data provider for creating buy request for configurable products", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteDownloadableLinks/composer.json b/app/code/Magento/QuoteDownloadableLinks/composer.json index 2353bf6dd2da4..246c8da17cc6e 100644 --- a/app/code/Magento/QuoteDownloadableLinks/composer.json +++ b/app/code/Magento/QuoteDownloadableLinks/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-downloadable-links", "description": "Magento module provides data provider for creating buy request for links of downloadable products", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 3bf4f1d4d2338..192b3701746ab 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/RelatedProductGraphQl/composer.json b/app/code/Magento/RelatedProductGraphQl/composer.json index e84d2a1472010..851377cebd956 100644 --- a/app/code/Magento/RelatedProductGraphQl/composer.json +++ b/app/code/Magento/RelatedProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php b/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php index 51394a5b7556b..997b9fd2f53d2 100644 --- a/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php +++ b/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php @@ -82,11 +82,9 @@ public function isVisible(array $arguments) $value = $this->cacheStorage->load($cacheKey); if ($value === false) { - $value = version_compare( - $this->viewerLogger->get($userId)->getLastViewVersion(), - $this->productMetadata->getVersion(), - '<' - ); + $lastViewVersion = $this->viewerLogger->get($userId)->getLastViewVersion(); + $value = ($lastViewVersion) ? + version_compare($lastViewVersion, $this->productMetadata->getVersion(), '<') : true; $this->cacheStorage->save(false, $cacheKey); } return (bool)$value; diff --git a/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php index ca910366868a9..1315912e96a30 100644 --- a/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php +++ b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php @@ -98,7 +98,7 @@ public function testIsVisible($expected, $version, $lastViewVersion) $this->sessionMock->expects($this->once()) ->method('getId') ->willReturn(1); - $this->productMetadataMock->expects($this->once()) + $this->productMetadataMock->expects($this->any()) ->method('getVersion') ->willReturn($version); $this->logMock->expects($this->once()) @@ -122,7 +122,7 @@ public function isVisibleProvider() return [ [false, '2.2.1-dev', '999.999.999-alpha'], [true, '2.2.1-dev', '2.0.0'], - [true, '2.2.1-dev', '2.2.0'], + [true, '2.2.1-dev', null], [false, '2.2.1-dev', '2.2.1'], [true, '2.2.1-dev', '2.2.0'], [true, '2.3.0', '2.2.0'], diff --git a/app/code/Magento/ReleaseNotification/composer.json b/app/code/Magento/ReleaseNotification/composer.json index a22d13772614f..b421b29ad6213 100644 --- a/app/code/Magento/ReleaseNotification/composer.json +++ b/app/code/Magento/ReleaseNotification/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-release-notification", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-user": "*", "magento/module-backend": "*", "magento/module-ui": "*", diff --git a/app/code/Magento/RemoteStorage/Driver/Adapter/Cache/Generic.php b/app/code/Magento/RemoteStorage/Driver/Adapter/Cache/Generic.php index 7adaf2de12e5b..018727134b608 100644 --- a/app/code/Magento/RemoteStorage/Driver/Adapter/Cache/Generic.php +++ b/app/code/Magento/RemoteStorage/Driver/Adapter/Cache/Generic.php @@ -168,6 +168,7 @@ public function deleteFile(string $path): void /** * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function deleteDir(string $dirname): void { @@ -194,7 +195,8 @@ public function getMetadata(string $path): ?array return null; } $meta = $this->serializer->unserialize($meta); - if (!$meta[$path]) { + + if (empty($meta[$path])) { return null; } $this->cacheData[$path] = $meta[$path]; diff --git a/app/code/Magento/RemoteStorage/Driver/Adapter/MetadataProvider.php b/app/code/Magento/RemoteStorage/Driver/Adapter/MetadataProvider.php index 421bddd657bbe..abca9cf815072 100644 --- a/app/code/Magento/RemoteStorage/Driver/Adapter/MetadataProvider.php +++ b/app/code/Magento/RemoteStorage/Driver/Adapter/MetadataProvider.php @@ -62,6 +62,7 @@ private function isDirectory($path): bool */ public function getMetadata(string $path): array { + // phpcs:disable Magento2.Functions.DiscouragedFunction $metadata = $this->cache->getMetadata($path); if (isset($metadata['type']) && ($metadata['type'] == 'dir' || $this->isMetadataComplete($metadata))) { return $metadata; @@ -104,8 +105,9 @@ public function getMetadata(string $path): array 'visibility' => $meta->visibility(), 'mimetype' => $meta->mimeType(), 'dirname' => dirname($meta->path()), - 'basename' => basename($meta->path()), + 'basename' => basename($meta->path(), '.' . pathinfo($path, PATHINFO_EXTENSION)), ]; + // phpcs:enable Magento2.Functions.DiscouragedFunction $extraMetadata = $meta->extraMetadata(); if (isset($extraMetadata['Metadata']['image-width']) && isset($extraMetadata['Metadata']['image-height'])) { $data['extra'] = $extraMetadata['Metadata']; diff --git a/app/code/Magento/RemoteStorage/Model/TmpFileCopier.php b/app/code/Magento/RemoteStorage/Model/TmpFileCopier.php new file mode 100644 index 0000000000000..0bc45127bcbbb --- /dev/null +++ b/app/code/Magento/RemoteStorage/Model/TmpFileCopier.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\RemoteStorage\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\RuntimeException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\TargetDirectory; +use Psr\Log\LoggerInterface; + +/** + * Copies file from remote to local tmp path + */ +class TmpFileCopier +{ + /** + * @var Filesystem\Directory\WriteInterface + */ + private $tmpDirectoryWrite; + + /** + * @var Filesystem\Directory\WriteInterface + */ + private $remoteDirectoryWrite; + + /** + * @var array + */ + private $tmpFiles = []; + + /** + * @var bool + */ + private $isEnabled; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param Filesystem $filesystem + * @param TargetDirectory $targetDirectory + * @param Config $config + * @param LoggerInterface $logger + * @throws FileSystemException + * @throws RuntimeException + */ + public function __construct( + Filesystem $filesystem, + TargetDirectory $targetDirectory, + Config $config, + LoggerInterface $logger + ) { + $this->tmpDirectoryWrite = $filesystem->getDirectoryWrite(DirectoryList::TMP); + $this->remoteDirectoryWrite = $targetDirectory->getDirectoryWrite(DirectoryList::ROOT); + $this->isEnabled = $config->isEnabled(); + $this->logger = $logger; + } + + /** + * Removes created tmp files + */ + public function __destruct() + { + try { + foreach ($this->tmpFiles as $key => $tmpFile) { + $this->tmpDirectoryWrite->delete($tmpFile); + unset($this->tmpFiles[$key]); + } + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } + } + + /** + * Moves file from the remote storage to tmp folder + * + * @param string $filePath + * @return string + * @throws FileSystemException + */ + public function copy(string $filePath): string + { + if (!$this->isEnabled) { + return $filePath; + } + + if (isset($this->tmpFiles[$filePath])) { + return $this->tmpFiles[$filePath]; + } + + $absolutePath = $this->remoteDirectoryWrite->getAbsolutePath($filePath); + if ($this->remoteDirectoryWrite->isFile($absolutePath)) { + $this->tmpDirectoryWrite->create(); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $tmpPath = $this->tmpDirectoryWrite->getAbsolutePath() . basename($filePath); + $content = $this->remoteDirectoryWrite->getDriver()->fileGetContents($filePath); + if ($this->tmpDirectoryWrite->getDriver()->filePutContents($tmpPath, $content) >= 0) { + $filePath = $tmpPath; + $this->tmpFiles[$tmpPath] = $tmpPath; + } + } + + return $filePath; + } +} diff --git a/app/code/Magento/RemoteStorage/Plugin/ExistingValidate.php b/app/code/Magento/RemoteStorage/Plugin/ExistingValidate.php index 46cb8ad9b69e4..0da9e711c229c 100644 --- a/app/code/Magento/RemoteStorage/Plugin/ExistingValidate.php +++ b/app/code/Magento/RemoteStorage/Plugin/ExistingValidate.php @@ -7,15 +7,10 @@ namespace Magento\RemoteStorage\Plugin; -use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Catalog\Model\Product\Option\Type\File\ExistingValidate as Subject; use Magento\Framework\Exception\FileSystemException; -use Magento\Framework\Exception\RuntimeException; -use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\Directory\TargetDirectory; use Magento\Framework\Image\Adapter\AbstractAdapter; -use Magento\RemoteStorage\Model\Config; -use Psr\Log\LoggerInterface; -use Magento\Catalog\Model\Product\Option\Type\File\ExistingValidate as Subject; +use Magento\RemoteStorage\Model\TmpFileCopier; /** * @see AbstractAdapter @@ -23,48 +18,17 @@ class ExistingValidate { /** - * @var Filesystem\Directory\WriteInterface - */ - private $tmpDirectoryWrite; - - /** - * @var Filesystem\Directory\WriteInterface - */ - private $remoteDirectoryWrite; - - /** - * @var array - */ - private $tmpFiles = []; - - /** - * @var bool - */ - private $isEnabled; - - /** - * @var LoggerInterface + * @var TmpFileCopier */ - private $logger; + private $tmpFileCopier; /** - * @param Filesystem $filesystem - * @param TargetDirectory $targetDirectory - * @param Config $config - * @param LoggerInterface $logger - * @throws FileSystemException - * @throws RuntimeException + * @param TmpFileCopier $tmpFileCopier */ public function __construct( - Filesystem $filesystem, - TargetDirectory $targetDirectory, - Config $config, - LoggerInterface $logger + TmpFileCopier $tmpFileCopier ) { - $this->tmpDirectoryWrite = $filesystem->getDirectoryWrite(DirectoryList::TMP); - $this->remoteDirectoryWrite = $targetDirectory->getDirectoryWrite(DirectoryList::ROOT); - $this->isEnabled = $config->isEnabled(); - $this->logger = $logger; + $this->tmpFileCopier = $tmpFileCopier; } /** @@ -79,51 +43,6 @@ public function __construct( */ public function beforeIsValid(Subject $subject, $value, string $originalName = null) { - if ($this->isEnabled) { - $value = $this->copyFileToTmp($value); - } - return [$value, $originalName]; - } - - /** - * Remove created tmp files - */ - public function __destruct() - { - try { - foreach ($this->tmpFiles as $key => $tmpFile) { - $this->tmpDirectoryWrite->delete($tmpFile); - unset($this->tmpFiles[$key]); - } - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); - } - } - - /** - * Move files from storage to tmp folder - * - * @param string $filePath - * @return string - * @throws FileSystemException - */ - private function copyFileToTmp(string $filePath): string - { - if (isset($this->tmpFiles[$filePath])) { - return $this->tmpFiles[$filePath]; - } - - $absolutePath = $this->remoteDirectoryWrite->getAbsolutePath($filePath); - if ($this->remoteDirectoryWrite->isFile($absolutePath)) { - $this->tmpDirectoryWrite->create(); - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $tmpPath = $this->tmpDirectoryWrite->getAbsolutePath() . basename($filePath); - $content = $this->remoteDirectoryWrite->getDriver()->fileGetContents($filePath); - if ($this->tmpDirectoryWrite->getDriver()->filePutContents($tmpPath, $content) >= 0) { - $filePath = $tmpPath; - $this->tmpFiles[$tmpPath] = $tmpPath; - } - } - return $filePath; + return [$this->tmpFileCopier->copy($value), $originalName]; } } diff --git a/app/code/Magento/RemoteStorage/Plugin/Filesystem/GetFileInfo.php b/app/code/Magento/RemoteStorage/Plugin/Filesystem/GetFileInfo.php new file mode 100644 index 0000000000000..58bbbf54ad6cd --- /dev/null +++ b/app/code/Magento/RemoteStorage/Plugin/Filesystem/GetFileInfo.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\RemoteStorage\Plugin\Filesystem; + +use Magento\Framework\Exception\FileSystemException; +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo as Subject; +use Magento\RemoteStorage\Model\TmpFileCopier; + +/** + * Copies file from the remote server to the tmp directory if remote storage is enabled + */ +class GetFileInfo +{ + /** + * @var TmpFileCopier + */ + private $tmpFileCopier; + + /** + * @param TmpFileCopier $tmpFileCopier + */ + public function __construct( + TmpFileCopier $tmpFileCopier + ) { + $this->tmpFileCopier = $tmpFileCopier; + } + + /** + * Copies file from the remote server to the tmp directory + * + * @param Subject $subject + * @param string $path + * @return array + * @throws FileSystemException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeExecute(Subject $subject, string $path) + { + return [$this->tmpFileCopier->copy($path)]; + } +} diff --git a/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/ExifReader.php b/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/ExifReader.php new file mode 100644 index 0000000000000..23414f17b7cb5 --- /dev/null +++ b/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/ExifReader.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\RemoteStorage\Plugin\MediaGalleryMetadata; + +use Magento\Framework\Exception\FileSystemException; +use Magento\MediaGalleryMetadata\Model\ExifReader as Subject; +use Magento\RemoteStorage\Model\TmpFileCopier; + +/** + * Copies file from the remote server to the tmp directory if remote storage is enabled + */ +class ExifReader +{ + /** + * @var TmpFileCopier + */ + private $tmpFileCopier; + + /** + * @param TmpFileCopier $tmpFileCopier + */ + public function __construct( + TmpFileCopier $tmpFileCopier + ) { + $this->tmpFileCopier = $tmpFileCopier; + } + + /** + * Copies file from the remote server to the tmp directory + * + * @param Subject $subject + * @param string $filePath + * @return array + * @throws FileSystemException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeGet(Subject $subject, string $filePath) + { + return [$this->tmpFileCopier->copy($filePath)]; + } +} diff --git a/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/IptcEmbed.php b/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/IptcEmbed.php new file mode 100644 index 0000000000000..7bde49f68e4db --- /dev/null +++ b/app/code/Magento/RemoteStorage/Plugin/MediaGalleryMetadata/IptcEmbed.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\RemoteStorage\Plugin\MediaGalleryMetadata; + +use Magento\Framework\Exception\FileSystemException; +use Magento\MediaGalleryMetadata\Model\IptcEmbed as Subject; +use Magento\RemoteStorage\Model\TmpFileCopier; + +/** + * Copies file from the remote server to the tmp directory if remote storage is enabled + */ +class IptcEmbed +{ + /** + * @var TmpFileCopier + */ + private $tmpFileCopier; + + /** + * @param TmpFileCopier $tmpFileCopier + */ + public function __construct( + TmpFileCopier $tmpFileCopier + ) { + $this->tmpFileCopier = $tmpFileCopier; + } + + /** + * Copies file from the remote server to the tmp directory + * + * @param Subject $subject + * @param string $iptcData + * @param string $filePath + * @return array + * @throws FileSystemException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeGet(Subject $subject, string $iptcData, string $filePath) + { + return [$iptcData, $this->tmpFileCopier->copy($filePath)]; + } +} diff --git a/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/GenericTest.php b/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/GenericTest.php new file mode 100644 index 0000000000000..0841521ebd7d6 --- /dev/null +++ b/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/GenericTest.php @@ -0,0 +1,127 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\RemoteStorage\Test\Unit\Driver\Adpater\Cache; + +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\RemoteStorage\Driver\Adapter\Cache\Generic; +use Magento\RemoteStorage\Driver\Adapter\PathUtil; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @see Generic + */ +class GenericTest extends TestCase +{ + /** + * @var Generic + */ + private Generic $generic; + + /** + * @var CacheInterface|MockObject + */ + private CacheInterface $cacheAdapterMock; + + /** + * @var SerializerInterface|MockObject + */ + private SerializerInterface $serializerMock; + + /** + * @var PathUtil|MockObject + */ + private PathUtil $pathUtilMock; + + protected function setUp(): void + { + $this->cacheAdapterMock = $this->createMock(CacheInterface::class); + $this->serializerMock = $this->createMock(SerializerInterface::class); + $this->pathUtilMock = $this->createMock(PathUtil::class); + + $this->generic = new Generic( + $this->cacheAdapterMock, + $this->serializerMock, + $this->pathUtilMock + ); + } + + /** + * @param string $input + * @param array|null $expectedOutput + * @dataProvider metaDataProvider + */ + public function testGetMetaData(string $input, ?array $expectedOutput): void + { + $cacheData = include __DIR__ . '/_files/CacheData.php'; + $this->cacheAdapterMock + ->expects($this->once()) + ->method('load') + ->willReturn(json_encode($cacheData)); + $this->serializerMock + ->expects($this->once()) + ->method('unserialize') + ->willReturn($cacheData); + + $this->assertEquals($expectedOutput, $this->generic->getMetaData($input)); + } + + /** + * @return array + */ + public function metaDataProvider(): array + { + return [ + [ + 'media', + [ + 'path' => 'media', + 'dirname' => '.', + 'basename' => 'media', + 'filename' => 'media', + 'type' => 'dir', + 'size' => null, + 'timestamp' => null, + 'visibility' => null, + 'mimetype' => '', + ], + ], + [ + 'media/tmp/catalog/product/1/test.jpeg', + [ + 'path' => 'media/tmp/catalog/product/1/test.jpeg', + 'dirname' => 'media/tmp/catalog/product/1', + 'basename' => 'test.jpeg', + 'extension' => 'jpeg', + 'filename' => 'test.jpeg', + 'type' => 'file', + 'size' => '87066', + 'timestamp' => '1635860865', + 'visibility' => null, + 'mimetype' => 'image/jpeg', + 'extra' => [ + 'image-width' => 680, + 'image-height' => 383, + ], + ], + ], + [ + 'media-nonexistent-path', + null, + ], + ]; + } + + protected function tearDown(): void + { + unset($this->generic); + unset($this->cacheAdapterMock); + unset($this->serializerMock); + unset($this->pathUtilMock); + } +} diff --git a/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/_files/CacheData.php b/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/_files/CacheData.php new file mode 100644 index 0000000000000..92e6d4dd69cc9 --- /dev/null +++ b/app/code/Magento/RemoteStorage/Test/Unit/Driver/Adpater/Cache/_files/CacheData.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +return [ + 'media' => [ + 'path' => 'media', + 'dirname' => '.', + 'basename' => 'media', + 'filename' => 'media', + 'type' => 'dir', + 'size' => null, + 'timestamp' => null, + 'visibility' => null, + 'mimetype' => '', + ], + 'media/tmp' => [ + 'path' => 'media/tmp', + 'dirname' => 'media', + 'basename' => 'tmp', + 'filename' => 'tmp', + 'type' => 'dir', + ], + 'media/tmp/catalog' => [ + 'path' => 'media/tmp/catalog', + 'dirname' => 'media/tmp', + 'basename' => 'catalog', + 'filename' => 'catalog', + 'type' => 'dir', + ], + 'media/tmp/catalog/product' => [ + 'path' => 'media/tmp/catalog/product', + 'dirname' => 'media/tmp/catalog', + 'basename' => 'product', + 'filename' => 'product', + 'type' => 'dir', + ], + 'media/tmp/catalog/product/1' => [ + 'path' => 'media/tmp/catalog/product/1', + 'dirname' => 'media/tmp/catalog/product', + 'basename' => '1', + 'filename' => '1', + 'type' => 'dir', + ], + 'media/tmp/catalog/product/1/test.jpeg' => [ + 'path' => 'media/tmp/catalog/product/1/test.jpeg', + 'dirname' => 'media/tmp/catalog/product/1', + 'basename' => 'test.jpeg', + 'extension' => 'jpeg', + 'filename' => 'test.jpeg', + 'type' => 'file', + 'size' => '87066', + 'timestamp' => '1635860865', + 'visibility' => null, + 'mimetype' => 'image/jpeg', + 'extra' => [ + 'image-width' => 680, + 'image-height' => 383, + ], + ], +]; diff --git a/app/code/Magento/RemoteStorage/composer.json b/app/code/Magento/RemoteStorage/composer.json index a69a114026003..a0b33346bce13 100644 --- a/app/code/Magento/RemoteStorage/composer.json +++ b/app/code/Magento/RemoteStorage/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-remote-storage", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { @@ -12,6 +12,8 @@ "magento/module-downloadable": "*", "magento/module-catalog": "*", "magento/module-media-storage": "*", + "magento/module-media-gallery-metadata": "*", + "magento/module-media-gallery-synchronization": "*", "magento/module-import-export": "*", "magento/module-catalog-import-export": "*", "magento/module-downloadable-import-export": "*", diff --git a/app/code/Magento/RemoteStorage/etc/di.xml b/app/code/Magento/RemoteStorage/etc/di.xml index f540e7433438c..130ed0f656344 100644 --- a/app/code/Magento/RemoteStorage/etc/di.xml +++ b/app/code/Magento/RemoteStorage/etc/di.xml @@ -82,6 +82,15 @@ <type name="Magento\MediaStorage\Model\File\Storage\SynchronizationFactory"> <plugin name="remoteMediaStorageSynchronizationFactory" type="Magento\RemoteStorage\Plugin\File\Storage\SynchronizationFactory" /> </type> + <type name="Magento\MediaGalleryMetadata\Model\IptcEmbed"> + <plugin name="remoteIptcEmbed" type="Magento\RemoteStorage\Plugin\MediaGalleryMetadata\IptcEmbed" /> + </type> + <type name="Magento\MediaGalleryMetadata\Model\ExifReader"> + <plugin name="remoteExifReader" type="Magento\RemoteStorage\Plugin\MediaGalleryMetadata\ExifReader" /> + </type> + <type name="Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo"> + <plugin name="remoteGetFileInfo" type="Magento\RemoteStorage\Plugin\Filesystem\GetFileInfo" /> + </type> <type name="Magento\Framework\Data\Collection\Filesystem"> <arguments> <argument name="filesystem" xsi:type="object">fullRemoteFilesystem</argument> diff --git a/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php b/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php index b564e582943ac..d7ef4b89b7072 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php @@ -24,7 +24,7 @@ protected function _getElementHtml(AbstractElement $element) { $_months = []; for ($i = 1; $i <= 12; $i++) { - $month = $this->_localeDate->date(mktime(null, null, null, $i, 1)) + $month = $this->_localeDate->date(mktime(0, 0, 0, $i, 1)) ->format('m'); $_months[$month] = $month; } diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php index d5d8d32744e49..8e0a3ab094497 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php @@ -30,7 +30,7 @@ abstract class AbstractReport extends \Magento\Backend\App\Action * * @see _isAllowed() */ - const ADMIN_RESOURCE = 'Magento_Reports::report'; + public const ADMIN_RESOURCE = 'Magento_Reports::report'; /** * @var \Magento\Framework\App\Response\Http\FileFactory @@ -100,7 +100,9 @@ protected function _getSession() */ public function _initAction() { + // phpcs:ignore Magento2.Legacy.ObsoleteResponse $this->_view->loadLayout(); + // phpcs:ignore Magento2.Legacy.ObsoleteResponse $this->_addBreadcrumb(__('Reports'), __('Reports')); return $this; } @@ -176,10 +178,9 @@ protected function _showLastExecutionTime($flagCode, $refreshCode) */ private function initFilterData(): \Magento\Framework\DataObject { - $requestData = $this->backendHelper - ->prepareFilterString( - $this->getRequest()->getParam('filter') - ); + $requestData = $this->backendHelper->prepareFilterString( + $this->getRequest()->getParam('filter', ''), + ); $filterRules = ['from' => $this->_dateFilter, 'to' => $this->_dateFilter]; $inputFilter = new \Zend_Filter_Input($filterRules, [], $requestData); diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml index c38307790589f..ad3530f93e919 100644 --- a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml @@ -40,9 +40,10 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> - <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteAttributeSet"> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> </actionGroup> + <comment userInput="BIC workaround" stepKey="deleteAttributeSet"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Reports/composer.json b/app/code/Magento/Reports/composer.json index 958de25e1aa48..a07d12285edbf 100644 --- a/app/code/Magento/Reports/composer.json +++ b/app/code/Magento/Reports/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/RequireJs/composer.json b/app/code/Magento/RequireJs/composer.json index 46033c23b16b8..0580119cf60d7 100644 --- a/app/code/Magento/RequireJs/composer.json +++ b/app/code/Magento/RequireJs/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/Review/Test/Mftf/Section/StorefrontMyProductReviewsSection.xml b/app/code/Magento/Review/Test/Mftf/Section/StorefrontMyProductReviewsSection.xml index 64c0c42df20a5..0275aa02fdcb9 100644 --- a/app/code/Magento/Review/Test/Mftf/Section/StorefrontMyProductReviewsSection.xml +++ b/app/code/Magento/Review/Test/Mftf/Section/StorefrontMyProductReviewsSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontMyProductReviewsSection"> <element name="reviewDescription" type="text" selector="//td[@data-th='Review']"/> <element name="reviewRating" type="text" selector="//tbody/tr[position()='{{reviewNumber}}']/td/div/div/span[contains(@style,'width: {{reviewValue}};')]" parameterized="true"/> + <element name="reviewSeeDetails" type="text" selector="#my-reviews-table > tbody > tr:nth-child({{row}}) > td.col.actions > a" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifyMultipleProductRatingsOnCategoryPageTest.xml b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifyMultipleProductRatingsOnCategoryPageTest.xml new file mode 100644 index 0000000000000..7e5a3b2a44ed3 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifyMultipleProductRatingsOnCategoryPageTest.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifyMultipleProductRatingsOnCategoryPageTest"> + <annotations> + <features value="Review"/> + <stories value="Review By Customers"/> + <title value="StoreFront inconsistent products rating on category page"/> + <description value="Check if product rating is the same at list, table view on PLP and customers account."/> + <severity value="AVERAGE"/> + <useCaseId value="ACP2E-111"/> + <testCaseId value="AC-1187"/> + </annotations> + <before> + <!-- Enable singe store view to view ratings --> + <magentoCLI command="config:set general/single_store_mode/enabled 1" stepKey="enabledSingleStoreMode"/> + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + <!-- Create product and category --> + <createData entity="SimpleSubCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="SimpleProduct" stepKey="createProduct2"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + <after> + <!-- Delete reviews --> + <actionGroup ref="AdminOpenReviewsPageActionGroup" stepKey="openAllReviewsPage"/> + <actionGroup ref="AdminDeleteReviewsByUserNicknameActionGroup" stepKey="deleteCustomerReview"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearNickNameReviewFilters"/> + <!-- Delete customer --> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="CustomerEntityOne.email"/> + </actionGroup> + <!-- delete Category and Products --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!-- Logout --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <!-- Disable single store view back --> + <magentoCLI command="config:set general/single_store_mode/enabled 0" stepKey="enabledSingleStoreMode"/> + </after> + + <!-- Go to frontend and make a user account and login with it --> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="signUpNewUser"> + <argument name="Customer" value="CustomerEntityOne"/> + </actionGroup> + <!-- Go to the first product view page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage1"> + <argument name="productUrl" value="$$createProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <!-- Click on reviews and add reviews with current user --> + <click selector="{{StorefrontProductReviewsSection.reviewsTab}}" stepKey="openReviewTab1"/> + <!-- Set product rating stars --> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setQualityStars1"> + <argument name="ratingName" value="Quality"/> + <argument name="stars" value="3"/> + </actionGroup> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setValueStars1"> + <argument name="ratingName" value="Value"/> + <argument name="stars" value="4"/> + </actionGroup> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setPriceStars1"> + <argument name="ratingName" value="Price"/> + <argument name="stars" value="5"/> + </actionGroup> + <!-- Click on reviews and add reviews with current user --> + <actionGroup ref="StorefrontAddProductReviewActionGroup" stepKey="addReview1"/> + <!-- Go to second product view page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage2"> + <argument name="productUrl" value="$$createProduct2.custom_attributes[url_key]$$"/> + </actionGroup> + <!-- Click on reviews and add reviews with current user --> + <click selector="{{StorefrontProductReviewsSection.reviewsTab}}" stepKey="openReviewTab2"/> + <!-- Set product rating stars --> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setQualityStars2"> + <argument name="ratingName" value="Quality"/> + <argument name="stars" value="1"/> + </actionGroup> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setValueStars2"> + <argument name="ratingName" value="Value"/> + <argument name="stars" value="1"/> + </actionGroup> + <actionGroup ref="StorefrontSetProductRatingStarsActionGroup" stepKey="setPriceStars2"> + <argument name="ratingName" value="Price"/> + <argument name="stars" value="1"/> + </actionGroup> + <!-- Add review --> + <actionGroup ref="StorefrontAddProductReviewActionGroup" stepKey="addReview2"/> + <!-- Approve all reviews --> + <actionGroup ref="AdminOpenPendingReviewsPageActionGroup" stepKey="openPendingReviewsPage"/> + <actionGroup ref="AdminApproveAllReviewsActionGroup" stepKey="approveAllCustomerReview"/> + <!--Start Checking reviews --> + <!-- Navigate to PLP and check product rating for list and table views --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="amOnStoreViewHomePage"/> + <!-- Open products in category section --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="goToStorefrontCategoryPage"> + <argument name="categoryName" value="$$category.name$$" /> + </actionGroup> + <!-- Grid mode is default --> + <!-- Check stars at grid view for first product --> + <grabAttributeFrom selector="#rating-result_$$createProduct1.id$$ span" userInput="style" stepKey="getFirstProductStarsAtGridView"/> + <assertEquals stepKey="checkFirstProductStarsAtGridView"> + <actualResult type="string">$getFirstProductStarsAtGridView</actualResult> + <expectedResult type="string">width: 80%;</expectedResult> + </assertEquals> + <!-- Check stars at grid view for second product --> + <grabAttributeFrom selector="#rating-result_$$createProduct2.id$$ span" userInput="style" stepKey="getSecondProductStarsAtGridView"/> + <assertEquals stepKey="checkSecondProductStarsAtGridView"> + <actualResult type="string">$getSecondProductStarsAtGridView</actualResult> + <expectedResult type="string">width: 20%;</expectedResult> + </assertEquals> + <!-- Switch category view to list mode --> + <actionGroup ref="StorefrontSwitchCategoryViewToListModeActionGroup" stepKey="switchCategoryViewToListMode"/> + <!-- Check stars at list view for first product --> + <grabAttributeFrom selector="#rating-result_$$createProduct1.id$$ span" userInput="style" stepKey="getFirstProductStarsAtListView"/> + <assertEquals stepKey="checkFirstProductStarsAtListView"> + <actualResult type="string">$getFirstProductStarsAtListView</actualResult> + <expectedResult type="string">width: 80%;</expectedResult> + </assertEquals> + <!-- Check stars at list view for second product --> + <grabAttributeFrom selector="#rating-result_$$createProduct2.id$$ span" userInput="style" stepKey="getSecondProductStarsAtListView"/> + <assertEquals stepKey="checkSecondProductStarsAtListView"> + <actualResult type="string">$getSecondProductStarsAtListView</actualResult> + <expectedResult type="string">width: 20%;</expectedResult> + </assertEquals> + <!-- Navigate to user account and check product ratings --> + <!-- Checking that all 3 reviews on the My Product Reviews page have one star ratings --> + <actionGroup ref="StorefrontNavigateToMyProductReviewsPageActionGroup" stepKey="navigateToProductReviewsPage"/> + <seeElement selector="{{StorefrontMyProductReviewsSection.reviewRating('2', '80%')}}" stepKey="seeFirstOneStarReviewOnMyReviews"/> + <seeElement selector="{{StorefrontMyProductReviewsSection.reviewRating('1', '20%')}}" stepKey="seeSecondOneStarReviewOnMyReviews"/> + <!-- Click on see details button of two reviews --> + <!-- Navigate to user account and check product ratings --> + <amOnPage url="review/customer/" stepKey="amOnCustomerReviewPage2"/> + <!-- Click on second product review --> + <click selector="{{StorefrontMyProductReviewsSection.reviewSeeDetails('1')}}" stepKey="clickFirstReviewRow"/> + <grabAttributeFrom selector="#rating-result_$$createProduct2.id$$ span" userInput="style" stepKey="getSecondProductResultStarsUnderProductName1"/> + <assertEquals stepKey="checkSecondProductResultStarsUnderProductName1"> + <actualResult type="string">$getSecondProductResultStarsUnderProductName1</actualResult> + <expectedResult type="string">width: 20%;</expectedResult> + </assertEquals> + <!-- Navigate to user account and check product ratings --> + <amOnPage url="review/customer/" stepKey="amOnCustomerReviewPage3"/> + <!-- Click on first product review --> + <click selector="{{StorefrontMyProductReviewsSection.reviewSeeDetails('2')}}" stepKey="clickSecondReviewRow"/> + <grabAttributeFrom selector="#rating-result_$$createProduct1.id$$ span" userInput="style" stepKey="getFirstProductResultStarsUnderProductName2"/> + <assertEquals stepKey="checkFirstProductResultStarsUnderProductName2"> + <actualResult type="string">$getFirstProductResultStarsUnderProductName2</actualResult> + <expectedResult type="string">width: 80%;</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Review/composer.json b/app/code/Magento/Review/composer.json index 3c4fe373b5e59..ddc1e0634540f 100644 --- a/app/code/Magento/Review/composer.json +++ b/app/code/Magento/Review/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Review/view/frontend/templates/customer/list.phtml b/app/code/Magento/Review/view/frontend/templates/customer/list.phtml index d44dc203dab85..f4a7e67c9f312 100644 --- a/app/code/Magento/Review/view/frontend/templates/customer/list.phtml +++ b/app/code/Magento/Review/view/frontend/templates/customer/list.phtml @@ -44,6 +44,7 @@ $reviewHelper = $block->getData('reviewHelper'); <div class="rating-summary"> <span class="label"><span><?= $escaper->escapeHtml(__('Rating')) ?>:</span></span> <div class="rating-result" + id="rating-result_<?= /* @noEscape */ $block->escapeHtml($review->getId()) ?>" title="<?= /* @noEscape */ ((int)$review->getSum() / (int)$review->getCount()) ?>%"> <span class="rating_<?= $escaper->escapeUrl($review->getReviewId())?>"> <span> @@ -52,9 +53,11 @@ $reviewHelper = $block->getData('reviewHelper'); </span> </div> </div> - <?= /* @noEscape */ $secureRenderer->renderStyleAsTag( + <?= /* @noEscape */ + $secureRenderer->renderStyleAsTag( "width:" . /* @noEscape */ ((int)$review->getSum() / (int)$review->getCount()) . "%;", - 'div.rating-summary div.rating-result>span.rating_' . $escaper->escapeUrl($review->getReviewId()) + 'div.rating-summary div.rating-result>span.rating_' . + $escaper->escapeUrl($review->getReviewId()) ) ?> <?php endif; ?> </td> diff --git a/app/code/Magento/Review/view/frontend/templates/customer/view.phtml b/app/code/Magento/Review/view/frontend/templates/customer/view.phtml index d1d9d3b7ccae7..7207c191202ce 100644 --- a/app/code/Magento/Review/view/frontend/templates/customer/view.phtml +++ b/app/code/Magento/Review/view/frontend/templates/customer/view.phtml @@ -43,14 +43,15 @@ $product = $block->getProductData(); <span class="rating-label"> <span><?= $block->escapeHtml($_rating->getRatingCode()) ?></span> </span> - <div class="rating-result" + <div class="rating-result <?= $block->escapeHtml($_rating->getRatingCode()) ?>" id="rating-div-<?= $block->escapeHtml($ratingId) ?>" title="<?= /* @noEscape */ $rating ?>%"> <span> <span><?= /* @noEscape */ $rating ?>%</span> </span> </div> - <?= /* @noEscape */ $secureRenderer->renderStyleAsTag( + <?= /* @noEscape */ + $secureRenderer->renderStyleAsTag( "width:" . /* @noEscape */ $rating . "%", 'div#rating-div-'.$_rating->getRatingId(). '>span:first-child' diff --git a/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml b/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml index 93afe4a815f61..c5ec6bc3f63a5 100644 --- a/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml +++ b/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml @@ -19,7 +19,10 @@ $urlForm = $block->getReviewsUrl() . '#review-form'; <?php if ($rating):?> <div class="rating-summary"> <span class="label"><span><?= $block->escapeHtml(__('Rating')) ?>:</span></span> - <div class="rating-result" title="<?= $block->escapeHtmlAttr($rating); ?>%"> + <div class="rating-result" + id="rating-result_<?= $block->escapeHtmlAttr($block->getProduct()->getId()) ?>" + title="<?= $block->escapeHtmlAttr($rating) ?>%" + > <span> <span> <span itemprop="ratingValue"><?= $block->escapeHtml($rating); ?> @@ -28,9 +31,10 @@ $urlForm = $block->getReviewsUrl() . '#review-form'; </span> </div> </div> - <?= /* @noEscape */ $secureRenderer->renderStyleAsTag( - "width:" . $block->escapeHtmlAttr($rating) . "%", - 'div.rating-summary div.rating-result>span:first-child' + <?= /* @noEscape */ + $secureRenderer->renderStyleAsTag( + 'width:' . $block->escapeHtmlAttr($rating) . '%', + '#rating-result_' . $block->getProduct()->getId() . ' span' ) ?> <?php endif;?> <div class="reviews-actions"> diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json index 75d8f01f78d87..324994ea00227 100644 --- a/app/code/Magento/ReviewAnalytics/composer.json +++ b/app/code/Magento/ReviewAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-review-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-review": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/ReviewGraphQl/composer.json b/app/code/Magento/ReviewGraphQl/composer.json index d861f71b8509f..0c9314baef197 100644 --- a/app/code/Magento/ReviewGraphQl/composer.json +++ b/app/code/Magento/ReviewGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/module-catalog": "*", "magento/module-review": "*", "magento/module-store": "*", diff --git a/app/code/Magento/Robots/composer.json b/app/code/Magento/Robots/composer.json index 6cf3c63783809..d27f8b9335e51 100644 --- a/app/code/Magento/Robots/composer.json +++ b/app/code/Magento/Robots/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Rss/composer.json b/app/code/Magento/Rss/composer.json index 1fe31bc2c8c00..805e664e12619 100644 --- a/app/code/Magento/Rss/composer.json +++ b/app/code/Magento/Rss/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Rule/composer.json b/app/code/Magento/Rule/composer.json index cd4292b7d6feb..8011037690800 100644 --- a/app/code/Magento/Rule/composer.json +++ b/app/code/Magento/Rule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AddSimpleProductToOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AddSimpleProductToOrderActionGroup.xml index f9294a779bf58..f31f5bbdaf5a5 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AddSimpleProductToOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AddSimpleProductToOrderActionGroup.xml @@ -22,9 +22,10 @@ <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearch"/> <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectProduct"/> + <waitForPageLoad stepKey="waitForProductLoad"/> <fillField selector="{{AdminOrderFormItemsSection.rowQty('1')}}" userInput="{{productQty}}" stepKey="fillProductQty"/> <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> - <waitForLoadingMaskToDisappear stepKey="waitForOptionsToLoad"/> + <waitForPageLoad stepKey="waitForOptionsToLoad"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAddSimpleProductWithCustomOptionFileToOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAddSimpleProductWithCustomOptionFileToOrderActionGroup.xml index 5b08ba30a8fec..3297e4a7a1cf6 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAddSimpleProductWithCustomOptionFileToOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAddSimpleProductWithCustomOptionFileToOrderActionGroup.xml @@ -16,9 +16,13 @@ </arguments> <remove keyForRemoval="fillProductQty"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad" after="selectProduct"/> - <fillField selector="{{AdminOrderFormCustomOptionsSection.quantity}}" userInput="{{productQty}}" stepKey="fillProductQty" after="waitForAjaxLoad"/> + <comment userInput="BIC workaround" stepKey="waitForAjaxLoad" after="waitForProductLoad"/> + <waitForElementVisible selector="{{AdminOrderFormCustomOptionsSection.quantity}}" stepKey="waitForProductQty" after="waitForAjaxLoad"/> + <fillField selector="{{AdminOrderFormCustomOptionsSection.quantity}}" userInput="{{productQty}}" stepKey="fillProductQty" after="waitForProductQty"/> <attachFile selector="{{AdminOrderFormCustomOptionsSection.file}}" userInput="{{file}}" stepKey="attachImageForOptional" after="fillProductQty"/> - <click selector="{{AdminOrderFormCustomOptionsSection.buttonOk}}" stepKey="clickButtonOK" after="attachImageForOptional"/> + <waitForPageLoad stepKey="waitForImageUploaded" after="attachImageForOptional"/> + <click selector="{{AdminOrderFormCustomOptionsSection.buttonOk}}" stepKey="clickButtonOK" after="waitForImageUploaded"/> + <waitForPageLoad stepKey="waitForSlideOutGone" after="clickButtonOK"/> + <waitForElementNotVisible selector="{{AdminOrderFormCustomOptionsSection.buttonOk}}" stepKey="waitForOkButtonGone" after="waitForSlideOutGone"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCancelAllOrdersFromGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCancelAllOrdersFromGridActionGroup.xml new file mode 100644 index 0000000000000..f0893b7ec62bd --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCancelAllOrdersFromGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminChangeStatusOfAllOrdersFromGridActionGroup"> + <annotations> + <description>Select all orders and change to specified status</description> + </annotations> + <arguments> + <argument name="status" defaultValue="Cancel" type="string"/> + </arguments> + <click selector="{{AdminOrdersGridSection.allCheckbox}}" stepKey="clickSelectAll"/> + <click selector="{{AdminOrdersGridSection.orderActions}}" stepKey="clickOnActionsDropdown"/> + <click selector="{{AdminOrdersGridSection.changeOrderStatus(status)}}" stepKey="clickStatus"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminChangeCustomerOptionFileActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminChangeCustomerOptionFileActionGroup.xml index fb9f68d861faf..14728a2c7ae32 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminChangeCustomerOptionFileActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminChangeCustomerOptionFileActionGroup.xml @@ -12,15 +12,19 @@ <description>Change custom option file on admin order page.</description> </annotations> <arguments> - <argument name="file" type="string" defaultValue="{{TestImageNew.file}}" /> + <argument name="file" type="string" defaultValue="{{TestImageNew.file}}"/> </arguments> <click selector="{{AdminOrderFormItemsSection.configure}}" stepKey="clickConfigure"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <waitForPageLoad stepKey="waitForAjaxLoad"/> + <waitForElementVisible selector="{{AdminOrderFormCustomOptionsSection.linkChange}}" stepKey="waitForLinkChange"/> <click selector="{{AdminOrderFormCustomOptionsSection.linkChange}}" stepKey="clickLinkChange"/> <waitForPageLoad stepKey="waitForChangeLoad"/> + <waitForElementVisible selector="{{AdminOrderFormCustomOptionsSection.file}}" stepKey="waitForAttachImage"/> <attachFile selector="{{AdminOrderFormCustomOptionsSection.file}}" userInput="{{file}}" stepKey="changeAttachImage"/> + <waitForPageLoad stepKey="waitForImageUploaded"/> <click selector="{{AdminOrderFormCustomOptionsSection.buttonOk}}" stepKey="clickButtonOK"/> <waitForPageLoad stepKey="waitForCustomOptionApplied"/> + <waitForElementNotVisible selector="{{AdminOrderFormCustomOptionsSection.buttonOk}}" stepKey="waitForOkButtonGone"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OpenOrderByIdActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/OpenOrderByIdActionGroup.xml index 16dacff3ecf81..6b5e00f5d2bdd 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OpenOrderByIdActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/OpenOrderByIdActionGroup.xml @@ -13,7 +13,7 @@ <description>EXTENDS: FilterOrderGridByIdActionGroup. Clicks on the 1st row of the Admin Orders grid.</description> </annotations> - <click selector="{{AdminDataGridTableSection.firstRow}}" after="clickOrderApplyFilters" stepKey="openOrderViewPage"/> + <click selector="{{AdminOrdersGridSection.exactOrderId(orderId)}}" after="clickOrderApplyFilters" stepKey="openOrderViewPage"/> <waitForPageLoad after="openOrderViewPage" stepKey="waitForOrderViewPageOpened"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationActionGroup.xml index 490496c1f1e9e..2a599823cc314 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationActionGroup.xml @@ -14,10 +14,11 @@ </annotations> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage"/> - <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderPendingStatus" after="seeSuccessMessage"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId" after="seeOrderPendingStatus"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getOrderId"> - <actualResult type="const">$getOrderId</actualResult> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderPendingStatus"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Helper/SalesHelper.php b/app/code/Magento/Sales/Test/Mftf/Helper/SalesHelper.php new file mode 100644 index 0000000000000..6c0d920b26000 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Helper/SalesHelper.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Mftf\Helper; + +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Magento\FunctionalTestingFramework\Helper\Helper; +use Facebook\WebDriver\Exception\NoSuchWindowException; + +/** + * Class for MFTF helpers for Sales module. + */ +class SalesHelper extends Helper +{ + private const COMPARISON_PATH_EXACT_MATCH = 'COMPARISON_PATH_EXACT_MATCH'; + private const COMPARISON_PATH_SUBSET_MATCH = 'COMPARISON_PATH_SUBSET_MATCH'; + + private const COMPARISON_MATCH_TYPES = [ + self::COMPARISON_PATH_EXACT_MATCH, + self::COMPARISON_PATH_SUBSET_MATCH + ]; + + /** + * Iterate through all available window handles and attach webdriver to window matching $expectedUrl. + * If print dialog is found, close it to prevent selenium from hanging and becoming unresponsive. + * + * @param string $expectedUrl + * @param string $expectedUrlComparisonType + * @throws \Codeception\Exception\ModuleException + */ + public function switchToWindowWithUrlAndClosePrintDialogIfEncountered( + string $expectedUrl, + string $expectedUrlComparisonType + ) { + if (!in_array($expectedUrlComparisonType, self::COMPARISON_MATCH_TYPES)) { + $this->fail('Expected URL comparison match type is not valid'); + } + + $magentoWebDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + + $webDriver = $magentoWebDriver->webDriver; + + // Pressing escape blurs the window and "unfreezes" chromedriver when it switches context back to chrome://print + try { + $magentoWebDriver->pressKey('body', [\Facebook\WebDriver\WebDriverKeys::ESCAPE]); + } catch (NoSuchWindowException $e) { + // This caught exception cannot be explained; no windows are closed as a result of this action; proceed + } + + $targetWindowHandle = null; + $availableWindowHandles = $webDriver->getWindowHandles(); + + foreach ($availableWindowHandles as $availableWindowHandle) { + $webDriver->switchTo()->window($availableWindowHandle); + + if ($webDriver->getCurrentURL() === 'chrome://print/') { + try { + // this escape press actually closes the print dialog + // the previous escape press is necessary for this press to close the dialog + $magentoWebDriver->pressKey('body', [\Facebook\WebDriver\WebDriverKeys::ESCAPE]); + } catch (NoSuchWindowException $e) { + // Print dialog successfully closes when requested in selenium, + // yet missing window message is sent back in the response + // when it evaluates the value on the element after the press; proceed + } + + // selenium is now effectively detached from any window; attach to an available window handle in case + // "fail" method is called and MFTF "after"/teardown steps need to be executed + $webDriver->switchTo()->window($webDriver->getWindowHandles()[0]); + + continue; + } + + $isWebDriverOnExpectedUrl = $this->evaluateIsWebDriverOnExpectedUrl( + $webDriver, + $expectedUrl, + $expectedUrlComparisonType + ); + + if ($isWebDriverOnExpectedUrl) { + $targetWindowHandle = $webDriver->getWindowHandle(); + } + } + + if (!$targetWindowHandle) { + $this->fail('Could not find window handle with requested expected url'); + } + + // switch to target window handle + $webDriver->switchTo()->window($targetWindowHandle); + } + + /** + * Is $webDriver currently attached to a window that matches $expectedUrl? + * + * @param RemoteWebDriver $webDriver + * @param string $expectedUrl + * @param string $expectedUrlComparisonType + * @return bool + */ + private function evaluateIsWebDriverOnExpectedUrl( + RemoteWebDriver $webDriver, + string $expectedUrl, + string $expectedUrlComparisonType + ): bool { + $currentWebDriverUrlPath = parse_url($webDriver->getCurrentURL(), PHP_URL_PATH); + + switch ($expectedUrlComparisonType) { + case self::COMPARISON_PATH_EXACT_MATCH: + $isWebDriverOnExpectedUrl = $currentWebDriverUrlPath === $expectedUrl; + break; + case self::COMPARISON_PATH_SUBSET_MATCH: + default: + $isWebDriverOnExpectedUrl = strpos($currentWebDriverUrlPath, $expectedUrl) !== false; + break; + } + + return $isWebDriverOnExpectedUrl; + } +} diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml index 55cd30f1caf2e..620aece7bf4a6 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml @@ -24,6 +24,7 @@ <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> <element name="rowViewAction" type="button" selector=".data-grid tbody > tr:nth-of-type({{row}}) .action-menu-item" parameterized="true" timeout="30"/> <element name="createNewOrder" type="button" selector=".page-actions-buttons button#add" timeout="30"/> + <element name="allCheckbox" type="checkbox" selector="//div[@data-role='grid-wrapper']//label[@data-bind='attr: {for: ko.uid}']" timeout="30"/> <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> <element name="gridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> @@ -41,6 +42,7 @@ <element name="viewLink" type="text" selector="//td/div[contains(.,'{{orderID}}')]/../..//a[@class='action-menu-item']" parameterized="true"/> <element name="selectOrderID" type="checkbox" selector="//td/div[text()='{{orderId}}']/../preceding-sibling::td//input" parameterized="true" timeout="60"/> <element name="orderId" type="text" selector="//table[contains(@class, 'data-grid')]//div[contains(text(), '{{orderId}}')]" parameterized="true"/> + <element name="exactOrderId" type="text" selector="//table[contains(@class, 'data-grid')]//div[text()='{{orderId}}']" parameterized="true"/> <element name="orderIdByIncrementId" type="text" selector="//input[@class='admin__control-checkbox' and @value={{incrId}}]/parent::label/parent::td/following-sibling::td" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml index f1346939354d4..af98ac1a04d2d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml @@ -63,7 +63,8 @@ <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <comment userInput="BIC workaround" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Cancel the Order --> <actionGroup ref="CancelPendingOrderActionGroup" stepKey="cancelPendingOrder"/> @@ -77,7 +78,7 @@ <click selector="{{StorefrontCustomerSidebarSection.sidebarCurrentTab('My Orders')}}" stepKey="clickOnMyOrders"/> <waitForPageLoad stepKey="waitForOrderDetailsToLoad"/> <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> <argument name="status" value="Canceled"/> </actionGroup> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml index ec423741db5d1..a3c3ff90f39a9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml @@ -149,7 +149,8 @@ <!-- Verify order information --> <comment userInput="Verify order information" stepKey="verifyOrderInformationComment"/> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <comment userInput="BIC workaround" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Cancel the Order --> <comment userInput="Cancel the Order" stepKey="cancelTheOrder"/> <actionGroup ref="CancelPendingOrderActionGroup" stepKey="cancelPendingOrder"/> @@ -216,12 +217,12 @@ <!-- Open Order Index Page --> <comment userInput="Open Order Index Page" stepKey="openOrderIndexPageComemnt"/> <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrders"/> - <!-- Filter order using orderId --> - <comment userInput="Filter order using orderId" stepKey="filterOrderUsingOrderIdComment"/> + <!-- Filter order using order number --> + <comment userInput="Filter order using order number" stepKey="filterOrderUsingOrderIdComment"/> <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="{$orderNumber}"/> </actionGroup> - <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="$orderId" stepKey="seeOrderIdInGrid"/> + <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="$orderNumber" stepKey="seeOrderIdInGrid"/> <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="Canceled" stepKey="seeStatusInGrid"/> <!-- Log in to Storefront as Customer --> <comment userInput="Log in to Storefront as Customer" stepKey="logInAsCustomerComment"/> @@ -233,7 +234,7 @@ <click selector="{{StorefrontCustomerSidebarSection.sidebarCurrentTab('My Orders')}}" stepKey="clickOnMyOrders"/> <waitForPageLoad stepKey="waitForOrderDetailsToLoad"/> <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> <argument name="status" value="Canceled"/> </actionGroup> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml index f47513bcadedd..60b4403c3ccee 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml @@ -62,7 +62,8 @@ <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Assert Simple Product Quantity in backend after order --> <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="filterAndSelectTheProduct"> @@ -76,11 +77,12 @@ <!-- Open Order Index Page --> <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrders"/> - <!-- Filter Order using orderId --> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById"> - <argument name="orderId" value="$orderId"/> + <!-- Open Order --> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminOrdersGridSection.rowViewAction('1')}}" stepKey="clickOnViewAction"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById"/> + <comment userInput="BIC workaround" stepKey="clickOnViewAction"/> <!-- Cancel the Order --> <actionGroup ref="CancelPendingOrderActionGroup" stepKey="cancelPendingOrder"/> @@ -97,12 +99,13 @@ <!-- Open Order Index Page --> <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrders1"/> - <!-- Filter Order using orderId --> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById1"> - <argument name="orderId" value="$orderId"/> + <!-- Open Order --> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder2"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminOrdersGridSection.rowViewAction('1')}}" stepKey="clickOnViewAction1"/> - <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridById1"/> + <comment userInput="BIC workaround" stepKey="clickOnViewAction1"/> + <comment userInput="BIC workaround" stepKey="waitForOrderPageToLoad"/> <!-- Reorder the product --> <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickOnReorderButton"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml index a4660bdeb0ad0..20ea41ef68fd9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml @@ -69,7 +69,8 @@ <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <comment userInput="BIC workaround" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Cancel the Order --> <actionGroup ref="CancelPendingOrderActionGroup" stepKey="cancelPendingOrder"/> @@ -83,7 +84,7 @@ <click selector="{{StorefrontCustomerSidebarSection.sidebarCurrentTab('My Orders')}}" stepKey="clickOnMyOrders"/> <waitForPageLoad stepKey="waitForOrderDetailsToLoad"/> <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> <argument name="status" value="Canceled"/> </actionGroup> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml index dd25610a7aad3..5078807ba3eba 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml @@ -70,7 +70,8 @@ <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> <reloadPage stepKey="refreshPage"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <comment userInput="BIC workaround" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Refresh the page --> <reloadPage stepKey="refreshPageAgain"/> @@ -87,7 +88,7 @@ <click selector="{{StorefrontCustomerSidebarSection.sidebarCurrentTab('My Orders')}}" stepKey="clickOnMyOrders"/> <waitForPageLoad stepKey="waitForOrderDetailsToLoad"/> <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> <argument name="status" value="Canceled"/> </actionGroup> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml index b9fb95f7bd36d..b6ac42e7eaea8 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml @@ -27,16 +27,16 @@ <createData entity="CustomerCart" stepKey="createCustomerCart"> <requiredEntity createDataKey="createCustomer"/> </createData> - + <createData entity="CustomerCartItem" stepKey="addCartItem"> <requiredEntity createDataKey="createCustomerCart"/> <requiredEntity createDataKey="createSimpleProduct"/> </createData> - + <createData entity="CustomerAddressInformation" stepKey="addCustomerOrderAddress"> <requiredEntity createDataKey="createCustomerCart"/> </createData> - + <updateData createDataKey="createCustomerCart" entity="CustomerOrderPaymentMethod" stepKey="sendCustomerPaymentInformation"> <requiredEntity createDataKey="createCustomerCart"/> </updateData> @@ -74,6 +74,6 @@ <click selector="{{AdminOrderDetailsOrderViewSection.creditMemos}}" stepKey="clickCreditMemosTab"/> <waitForPageLoad stepKey="waitForCreditMemosGridToLoad"/> - <see selector="{{AdminOrderCreditMemosTabSection.gridRow('1')}}" userInput="$123" stepKey="seeCreditMemoInGrid"/> + <waitForText selector="{{AdminOrderCreditMemosTabSection.gridRow('1')}}" userInput="$123" stepKey="seeCreditMemoInGrid"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoBankTransferPaymentTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoBankTransferPaymentTest.xml index add2c03fe0172..141bf27c8f184 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoBankTransferPaymentTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoBankTransferPaymentTest.xml @@ -68,12 +68,14 @@ <see selector="{{AdminMessagesSection.success}}" userInput="The invoice has been created." stepKey="seeInvoiceCreateSuccess"/> <!-- Go to Sales > Orders > find out placed order and open --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="grabOrderId" /> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="grabOrderId"> - <actualResult type="const">$grabOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="grabOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Click 'Credit Memo' button and fill data from dataset: refund --> @@ -117,7 +119,7 @@ <!--Assert refund in refunds grid--> <actionGroup ref="AdminAssertRefundInRefundsGridActionGroup" stepKey="assertRefund"> - <argument name="orderId" value="{$grabOrderId}"/> + <argument name="orderId" value="{$orderNumber}"/> <argument name="memoId" value="{$grabMemoId}"/> <argument name="refundStatus" value="Refunded"/> <argument name="refundedTotal" value="$555.00"/> @@ -131,7 +133,7 @@ <!-- Assert refunded Grand Total on frontend --> <actionGroup ref="StorefrontOpenMyAccountPageActionGroup" stepKey="onAccountPage"/> <scrollTo selector="{{StorefrontCustomerResentOrdersSection.blockResentOrders}}" stepKey="scrollToResent"/> - <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$grabOrderId})}}" stepKey="clickOnOrder"/> + <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$orderNumber})}}" stepKey="clickOnOrder"/> <waitForPageLoad stepKey="waitForViewOrder"/> <actionGroup ref="StorefrontClickRefundTabCustomerOrderViewActionGroup" stepKey="clickRefund"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="scrollToGrandTotal"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml index c38acd72ca299..4e9c9a0257e33 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml @@ -66,7 +66,7 @@ <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="clearFilters"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="grabOrderId"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="filterOrdersGridById"/> - + <actionGroup ref="AdminOpenAndFillCreditMemoRefundActionGroup" stepKey="fillCreditMemoRefund"> <argument name="itemQtyToRefund" value="1"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml index 698b9e9bd46a7..e0fd7efa60b71 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml @@ -63,12 +63,14 @@ <see selector="{{AdminMessagesSection.success}}" userInput="The invoice has been created." stepKey="seeInvoiceCreateSuccess"/> <!-- Go to Sales > Orders > find out placed order and open --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="grabOrderId" /> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="grabOrderId"> - <actualResult type="const">$grabOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="grabOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Click 'Credit Memo' button and fill data from dataset: partial refund --> @@ -107,8 +109,8 @@ <see userInput="1" selector="{{AdminCreditMemoViewItemsSection.productQty}}" stepKey="seeQty"/> <!-- Go to order page --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderPage"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderPage"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Assert refund order status in Comments History --> @@ -125,7 +127,7 @@ <!-- Assert refunded Grand Total on frontend --> <actionGroup ref="StorefrontOpenMyAccountPageActionGroup" stepKey="onAccountPage"/> <scrollTo selector="{{StorefrontCustomerResentOrdersSection.blockResentOrders}}" stepKey="scrollToResent"/> - <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$grabOrderId})}}" stepKey="clickOnOrder"/> + <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$orderNumber})}}" stepKey="clickOnOrder"/> <waitForPageLoad stepKey="waitForViewOrder"/> <actionGroup ref="StorefrontClickRefundTabCustomerOrderViewActionGroup" stepKey="clickRefund"/> <scrollTo selector="{{StorefrontCustomerOrderSection.grandTotalRefund}}" stepKey="scrollToGrandTotal"/> @@ -142,7 +144,7 @@ <!--Assert refund in refunds grid--> <actionGroup ref="AdminAssertRefundInRefundsGridActionGroup" stepKey="assertRefund"> - <argument name="orderId" value="{$grabOrderId}"/> + <argument name="orderId" value="{$orderNumber}"/> <argument name="memoId" value="{$grabMemoId}"/> <argument name="refundStatus" value="Refunded"/> <argument name="refundedTotal" value="$110.00"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithCashOnDeliveryTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithCashOnDeliveryTest.xml index 88d80c24803e3..36d319bf71125 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithCashOnDeliveryTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithCashOnDeliveryTest.xml @@ -17,10 +17,10 @@ <testCaseId value="MC-15863"/> <group value="sales"/> <group value="mtf_migrated"/> + <group value="pr_exclude"/> <skip> <issueId value="DEPRECATED">Use AdminCreateCreditMemoForOrderWithCashOnDeliveryTest instead</issueId> - </skip> - <group value="pr_exclude"/> + </skip> </annotations> <before> <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> @@ -74,12 +74,14 @@ <see selector="{{AdminMessagesSection.success}}" userInput="The invoice has been created." stepKey="seeInvoiceCreateSuccess"/> <!-- Go to Sales > Orders > find out placed order and open --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="grabOrderId" /> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="grabOrderId"> - <actualResult type="const">$grabOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="grabOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Click 'Credit Memo' button and fill data from dataset: refund --> @@ -122,7 +124,7 @@ <!-- Assert refunded Grand Total on frontend --> <actionGroup ref="StorefrontOpenMyAccountPageActionGroup" stepKey="onAccountPage"/> <scrollTo selector="{{StorefrontCustomerResentOrdersSection.blockResentOrders}}" stepKey="scrollToResent"/> - <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$grabOrderId})}}" stepKey="clickOnOrder"/> + <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$orderNumber})}}" stepKey="clickOnOrder"/> <waitForPageLoad stepKey="waitForViewOrder"/> <actionGroup ref="StorefrontClickRefundTabCustomerOrderViewActionGroup" stepKey="clickRefund"/> <scrollTo selector="{{StorefrontCustomerOrderSection.grandTotalRefund}}" stepKey="scrollToGrandTotal"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml index cdfbcf969c914..f122665c6fca7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml @@ -71,12 +71,14 @@ <see selector="{{AdminMessagesSection.success}}" userInput="The invoice has been created." stepKey="seeInvoiceCreateSuccess"/> <!-- Go to Sales > Orders > find out placed order and open --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="grabOrderId" /> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="grabOrderId"> - <actualResult type="const">$grabOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="grabOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="{$grabOrderId}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Click 'Credit Memo' button and fill data from dataset: refund --> @@ -117,7 +119,7 @@ <!-- Assert refunded Grand Total on frontend --> <actionGroup ref="StorefrontOpenMyAccountPageActionGroup" stepKey="onAccountPage"/> <scrollTo selector="{{StorefrontCustomerResentOrdersSection.blockResentOrders}}" stepKey="scrollToResent"/> - <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$grabOrderId})}}" stepKey="clickOnOrder"/> + <click selector="{{StorefrontCustomerResentOrdersSection.viewOrder({$orderNumber})}}" stepKey="clickOnOrder"/> <waitForPageLoad stepKey="waitForViewOrder"/> <actionGroup ref="StorefrontClickRefundTabCustomerOrderViewActionGroup" stepKey="clickRefund"/> <scrollTo selector="{{StorefrontCustomerOrderSection.grandTotalRefund}}" stepKey="scrollToGrandTotal"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml index 3a9b252a95a3d..219a3fcb4a04e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml @@ -80,8 +80,8 @@ <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="$grabOrderId"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> <actionGroup ref="AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup" stepKey="fillCreditMemoRefund"> <argument name="itemQtyToRefund" value="0"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderAndCheckTheReorderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderAndCheckTheReorderTest.xml index ab5c089dfcc30..6570280b1118b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderAndCheckTheReorderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderAndCheckTheReorderTest.xml @@ -39,9 +39,11 @@ <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderToVerifyApplyAndRemoveCouponCodeTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderToVerifyApplyAndRemoveCouponCodeTest.xml index 9aabc17edc610..903429e6a0b8f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderToVerifyApplyAndRemoveCouponCodeTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderToVerifyApplyAndRemoveCouponCodeTest.xml @@ -59,11 +59,12 @@ dependentSelector="{{AdminOrderFormPaymentSection.bankTransferOption}}" visible="true" stepKey="checkBankTransferOption"/> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage"/> - <waitForPageLoad stepKey="waitForOrdersPageLoad"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="waitForOrdersPageLoad"/> <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="{$orderNumber}"/> </actionGroup> <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml index 3b782aa4e22f4..649956ef8e1a2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml @@ -57,5 +57,6 @@ <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> <argument name="email" value="$generatedCustomerEmail"/> </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml index b0926948f2bf6..199fa01b25376 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml @@ -43,7 +43,7 @@ <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearch"/> <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectProduct"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <waitForPageLoad stepKey="waitForAjaxLoad"/> <executeJS function="{{AdminProductCustomizableOptionsSection.requiredFieldIndicator}}" stepKey="dateTimeRequiredFieldIndicator"/> <assertEquals message="pass" stepKey="assertRequiredFieldIndicator"> <actualResult type="variable">dateTimeRequiredFieldIndicator</actualResult> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml index 6b714e0d18726..63adb3eb814bd 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml @@ -49,7 +49,7 @@ <actionGroup ref="AdminFillAccountInformationOnCreateOrderPageActionGroup" stepKey="fillEmail"> <argument name="email" value="{{Simple_US_Customer.email}}"/> </actionGroup> - + <!--Fill customer address information--> <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> <argument name="customer" value="Simple_US_Customer"/> @@ -70,9 +70,10 @@ <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage"/> <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderPendingStatus"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <comment userInput="BIC workaround" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> - <actualResult type="const">$orderId</actualResult> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation"> <argument name="customer" value="Simple_US_Customer"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml index 82b9e8c207814..b19e1fc8eff9d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml @@ -50,6 +50,8 @@ <argument name="productQty" value="$simpleProduct.quantity$"/> </actionGroup> <!--Verify, admin able to change file for custom option.--> - <actionGroup ref="AdminChangeCustomerOptionFileActionGroup" stepKey="changeFile"/> + <actionGroup ref="AdminChangeCustomerOptionFileActionGroup" stepKey="changeFile"> + <argument name="file" value="{{TestImageAdobe.file}}"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductTest.xml index 1c8cf2219f13b..a26b2dcd06ae5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductTest.xml @@ -39,9 +39,11 @@ <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml index 1f604266c8960..10b911e2d8f2a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml @@ -64,7 +64,8 @@ <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Hold the Order --> <click selector="{{AdminOrderDetailsMainActionsSection.hold}}" stepKey="clickOnHoldButton"/> @@ -90,7 +91,7 @@ <click selector="{{StorefrontCustomerSidebarSection.sidebarCurrentTab('My Orders')}}" stepKey="clickOnMyOrders"/> <waitForPageLoad stepKey="waitForOrderDetailsToLoad"/> <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> <argument name="status" value="On Hold"/> </actionGroup> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelCompleteAndClosedTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelCompleteAndClosedTest.xml index 28f79a505aa89..bf90770ad849f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelCompleteAndClosedTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelCompleteAndClosedTest.xml @@ -17,10 +17,10 @@ <testCaseId value="MC-16183"/> <group value="sales"/> <group value="mtf_migrated"/> + <group value="pr_exclude"/> <skip> <issueId value="DEPRECATED">Use AdminMassOrdersCancelClosedAndCompleteTest instead</issueId> - </skip> - <group value="pr_exclude"/> + </skip> </annotations> <before> <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> @@ -45,9 +45,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getFirstOrderId"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getFirstOrderId"> - <actualResult type="const">$getFirstOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getFirstOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber1"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId1"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber1</actualResult> </assertNotEmpty> <!-- Create Shipment for first Order --> @@ -58,9 +60,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getSecondOrderId"/> - <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty" after="getSecondOrderId"> - <actualResult type="const">$getSecondOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getSecondOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber2"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId2"/> + <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber2</actualResult> </assertNotEmpty> <!-- Create CreditMemo for second Order --> @@ -73,25 +77,25 @@ <!-- Select Mass Action according to dataset: Cancel --> <actionGroup ref="AdminTwoOrderActionOnGridActionGroup" stepKey="massActionCancel"> <argument name="action" value="Cancel"/> - <argument name="orderId" value="{$getFirstOrderId}"/> - <argument name="secondOrderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> + <argument name="secondOrderId" value="{$orderNumber2}"/> </actionGroup> <see userInput="You cannot cancel the order(s)." stepKey="assertOrderCancelMassActionFailMessage"/> <!--Assert first order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeFirstOrder"> - <argument name="orderId" value="{$getFirstOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> <argument name="orderStatus" value="Complete"/> </actionGroup> - <see userInput="{$getFirstOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> + <see userInput="{$orderNumber1}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> <see userInput="Complete" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertFirstOrderStatus"/> <!--Assert second order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeSecondOrder"> - <argument name="orderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber2}"/> <argument name="orderStatus" value="Closed"/> </actionGroup> - <see userInput="{$getSecondOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> + <see userInput="{$orderNumber2}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> <see userInput="Closed" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertSecondStatus"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelProcessingAndClosedTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelProcessingAndClosedTest.xml index 49c5c6e076edb..0abfcb3c8df62 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelProcessingAndClosedTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelProcessingAndClosedTest.xml @@ -45,9 +45,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getFirstOrderId"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getFirstOrderId"> - <actualResult type="const">$getFirstOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getFirstOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber1"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId1"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber1</actualResult> </assertNotEmpty> <!-- Create Invoice for first Order --> @@ -58,9 +60,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getSecondOrderId"/> - <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty" after="getSecondOrderId"> - <actualResult type="const">$getSecondOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getSecondOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber2"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId2"/> + <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber2</actualResult> </assertNotEmpty> <!-- Create CreditMemo for second Order --> @@ -73,25 +77,25 @@ <!-- Select Mass Action according to dataset: Cancel --> <actionGroup ref="AdminTwoOrderActionOnGridActionGroup" stepKey="massActionCancel"> <argument name="action" value="Cancel"/> - <argument name="orderId" value="{$getFirstOrderId}"/> - <argument name="secondOrderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> + <argument name="secondOrderId" value="{$orderNumber2}"/> </actionGroup> <see userInput="You cannot cancel the order(s)." stepKey="assertOrderCancelMassActionFailMessage"/> <!--Assert first order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeFirstOrder"> - <argument name="orderId" value="{$getFirstOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> <argument name="orderStatus" value="Processing"/> </actionGroup> - <see userInput="{$getFirstOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> + <see userInput="{$orderNumber1}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> <see userInput="Processing" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertFirstOrderStatus"/> <!--Assert second order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeSecondOrder"> - <argument name="orderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber2}"/> <argument name="orderStatus" value="Closed"/> </actionGroup> - <see userInput="{$getSecondOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> + <see userInput="{$orderNumber2}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> <see userInput="Closed" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertSecondStatus"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnPendingAndProcessingTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnPendingAndProcessingTest.xml index 6150ff6723711..9d1840d18a97e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnPendingAndProcessingTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnPendingAndProcessingTest.xml @@ -45,9 +45,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getFirstOrderId"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getFirstOrderId"> - <actualResult type="const">$getFirstOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getFirstOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber1"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId1"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber1</actualResult> </assertNotEmpty> <!-- Create second order --> @@ -55,9 +57,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getSecondOrderId"/> - <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty" after="getSecondOrderId"> - <actualResult type="const">$getSecondOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getSecondOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber2"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId2"/> + <assertNotEmpty stepKey="assertSecondOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber2</actualResult> </assertNotEmpty> <!-- Create Invoice for second Order --> @@ -70,25 +74,25 @@ <!-- Select Mass Action according to dataset: Hold --> <actionGroup ref="AdminTwoOrderActionOnGridActionGroup" stepKey="massActionHold"> <argument name="action" value="Hold"/> - <argument name="orderId" value="{$getFirstOrderId}"/> - <argument name="secondOrderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> + <argument name="secondOrderId" value="{$orderNumber2}"/> </actionGroup> <see userInput="You have put 2 order(s) on hold." stepKey="assertOrderOnHoldSuccessMessage"/> <!--Assert first order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeFirstOrder"> - <argument name="orderId" value="{$getFirstOrderId}"/> + <argument name="orderId" value="{$orderNumber1}"/> <argument name="orderStatus" value="On Hold"/> </actionGroup> - <see userInput="{$getFirstOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> + <see userInput="{$orderNumber1}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertFirstOrderID"/> <see userInput="On Hold" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertFirstOrderStatus"/> <!--Assert second order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeSecondOrder"> - <argument name="orderId" value="{$getSecondOrderId}"/> + <argument name="orderId" value="{$orderNumber2}"/> <argument name="orderStatus" value="On Hold"/> </actionGroup> - <see userInput="{$getSecondOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> + <see userInput="{$orderNumber2}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertSecondOrderID"/> <see userInput="On Hold" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertSecondStatus"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml index 27ed62fee35e2..58e2443c5d5c0 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml @@ -41,9 +41,11 @@ <argument name="product" value="$$createProduct$$"/> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getOrderId"> - <actualResult type="const">$getOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> <!-- Navigate to backend: Go to Sales > Orders --> @@ -53,16 +55,16 @@ <!-- Select Mass Action according to dataset: Unhold --> <actionGroup ref="AdminOrderActionOnGridActionGroup" stepKey="actionUnhold"> <argument name="action" value="Unhold"/> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <see userInput="No order(s) were released from on hold status." stepKey="assertOrderReleaseFailMessage"/> <!--Assert order in orders grid --> <actionGroup ref="AdminOrderFilterByOrderIdAndStatusActionGroup" stepKey="seeFirstOrder"> - <argument name="orderId" value="{$getOrderId}"/> + <argument name="orderId" value="{$orderNumber}"/> <argument name="orderStatus" value="Pending"/> </actionGroup> - <see userInput="{$getOrderId}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertOrderID"/> + <see userInput="{$orderNumber}" selector="{{AdminOrdersGridSection.gridCell('1','ID')}}" stepKey="assertOrderID"/> <see userInput="Pending" selector="{{AdminOrdersGridSection.gridCell('1','Status')}}" stepKey="assertOrderStatus"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml index 1c3ab70857151..dc96da653d6d6 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderAddressNotSavedInAddressBookTest.xml @@ -44,11 +44,13 @@ <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="openCheckoutPage"/> <actionGroup ref="LoggedInUserCheckoutFillingShippingSectionActionGroup" stepKey="fillAddressForm"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> - <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Reorder created order --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderById"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <actionGroup ref="AdminStartReorderFromOrderPageActionGroup" stepKey="startReorder"/> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderProductWithCustomOptionsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderProductWithCustomOptionsTest.xml index 72034141fa6bd..32db7abceb6a7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderProductWithCustomOptionsTest.xml @@ -19,8 +19,8 @@ </annotations> <!-- Reorder created order --> - <actionGroup ref="OpenOrderByIdActionGroup" after="placeReorder" stepKey="adminOpenOrderById"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" after="placeReorder" stepKey="adminOpenOrderById"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <actionGroup ref="AdminStartReorderFromOrderPageActionGroup" after="adminOpenOrderById" stepKey="adminStartReorder"/> <actionGroup ref="AdminSubmitOrderActionGroup" after="adminStartReorder" stepKey="adminSubmitOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml index cd3b128b5c19c..121b1a13333af 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml @@ -22,6 +22,8 @@ <before> <!--Create product--> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRateShipping"/> + <magentoCLI command="config:set carriers/flatrate/price 5.00" stepKey="setFlatRateShippingPrice"/> <createData entity="SimpleProduct2" stepKey="createSimpleProductApi"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> @@ -54,7 +56,9 @@ </after> <!--Open order by Id--> - <amOnPage url="{{AdminOrderPage.url($createGuestCart.return$)}}" stepKey="navigateToOrderPage"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="navigateToOrderPage"> + <argument name="entityId" value="$createGuestCart.return$"/> + </actionGroup> <!--Reorder--> <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickReorder"/> <!--Verify order item row--> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml index 86049d248dd0c..0e80c6ee93e93 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml @@ -29,6 +29,10 @@ <magentoCLI stepKey="allowSpecificValue" command="config:set payment/cashondelivery/active 0" /> <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{Simple_US_Customer.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> <!--Create order via Admin--> @@ -56,7 +60,7 @@ <actionGroup ref="AdminFillAccountInformationOnCreateOrderPageActionGroup" stepKey="fillCustomerEmail" after="selectCustomerGroup"> <argument name="email" value="{{Simple_US_Customer.email}}"/> </actionGroup> - + <!--Fill customer address information--> <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress" after="fillCustomerEmail"> <argument name="customer" value="Simple_US_Customer"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AssignCustomOrderStatusNotVisibleOnStorefrontTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AssignCustomOrderStatusNotVisibleOnStorefrontTest.xml index ac17d41c43c3d..88e3ada61068a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AssignCustomOrderStatusNotVisibleOnStorefrontTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AssignCustomOrderStatusNotVisibleOnStorefrontTest.xml @@ -32,6 +32,9 @@ <after> <!-- Disable created order status --> <magentoCLI command="config:set {{EnableCheckmoOrderStatusPending.path}} {{EnableCheckmoOrderStatusPending.value}}" stepKey="rollbackNewOrderStatus"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> <!-- Logout customer --> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> @@ -42,6 +45,33 @@ <!-- Delete customer --> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <!-- Unassign order status --> + <!-- Must unassign status before canceling order in cases where test fails before creating the order but after creating the custom order status --> + <actionGroup ref="AdminGoToOrderStatusPageActionGroup" stepKey="goToOrderStatus"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForStatusPageLoad"/> + <actionGroup ref="FilterOrderStatusByLabelAndCodeActionGroup" stepKey="filterStatusGrid"> + <argument name="statusLabel" value="{{defaultOrderStatus.label}}"/> + <argument name="statusCode" value="{{defaultOrderStatus.status}}"/> + </actionGroup> + <click selector="{{AdminOrderStatusGridSection.unassign}}" stepKey="unassignOrderStatus"/> + <waitForPageLoad stepKey="waitForGridLoad"/> + + <!-- Cancel order --> + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToAdminOrdersPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> + <actionGroup ref="AdminChangeStatusOfAllOrdersFromGridActionGroup" stepKey="cancelOrder"/> + + <!-- Unassign order status --> + <!-- Must unassign status after canceling order in cases where test does not fail before creating the order --> + <actionGroup ref="AdminGoToOrderStatusPageActionGroup" stepKey="goToOrderStatus2"/> + <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForStatusPageLoad2"/> + <actionGroup ref="FilterOrderStatusByLabelAndCodeActionGroup" stepKey="filterStatusGrid2"> + <argument name="statusLabel" value="{{defaultOrderStatus.label}}"/> + <argument name="statusCode" value="{{defaultOrderStatus.status}}"/> + </actionGroup> + <click selector="{{AdminOrderStatusGridSection.unassign}}" stepKey="unassignOrderStatus2"/> + <waitForPageLoad stepKey="waitForGridLoad2"/> + <!-- Log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> @@ -69,6 +99,9 @@ <!-- Prepare data for constraints --> <magentoCLI command="config:set {{EnableCheckmoOrderStatusPending.path}} {{defaultOrderStatus.label}}" stepKey="enableNewOrderStatus"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> <!-- Assert order status in grid --> <actionGroup ref="FilterOrderStatusByLabelAndCodeActionGroup" stepKey="filterOrderStatusGrid"> @@ -78,18 +111,26 @@ <see selector="{{AdminOrderStatusGridSection.gridCell('1', 'State Code and Title')}}" userInput="new[{{defaultOrderStatus.label}}]" stepKey="seeOrderStatusInOrderGrid"/> <!-- Create order and grab order id --> - <actionGroup ref="CreateOrderActionGroup" stepKey="createNewOrder"> - <argument name="product" value="$$createSimpleProduct$$"/> + <actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToNewOrderWithExistingCustomer"> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSimpleProductToOrder"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AdminSelectFlatRateShippingMethodActionGroup" stepKey="orderSelectFlatRateShipping"/> + <actionGroup ref="SelectCheckMoneyPaymentMethodActionGroup" stepKey="selectCheckMoneyPayment"/> + <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="createNewOrder"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Assert order status is correct --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridById"/> + <comment userInput="BIC workaround" stepKey="clickCreatedOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="{{defaultOrderStatus.label}}" stepKey="seeOrderStatus"/> <!-- Login as customer --> @@ -106,26 +147,16 @@ <!-- Assert order not visible on My Orders --> <see selector="{{StorefrontOrderInformationMainSection.emptyMessage}}" userInput="You have placed no orders." stepKey="seeEmptyMessage"/> + <comment userInput="BIC workaround" stepKey="goToAdminOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridByOrderId"/> + <comment userInput="BIC workaround" stepKey="selectOrder"/> + <comment userInput="BIC workaround" stepKey="selectCancelOrderAction"/> + <comment userInput="BIC workaround" stepKey="seeSuccessMessage"/> + <comment userInput="BIC workaround" stepKey="goToOrderStatus"/> + <comment userInput="BIC workaround" stepKey="waitForStatusPageLoad"/> + <comment userInput="BIC workaround" stepKey="filterStatusGrid"/> + <comment userInput="BIC workaround" stepKey="unassignOrderStatus"/> + <comment userInput="BIC workaround" stepKey="seeMessage"/> - <!-- Cancel order --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToAdminOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridByOrderId"> - <argument name="orderId" value="$getOrderId"/> - </actionGroup> - <checkOption selector="{{AdminOrdersGridSection.checkOrder}}" stepKey="selectOrder"/> - <actionGroup ref="SelectActionForOrdersActionGroup" stepKey="selectCancelOrderAction"> - <argument name="action" value="{{OrderActions.cancel}}"/> - </actionGroup> - <see selector="{{AdminMessagesSection.success}}" userInput="We canceled 1 order(s)." stepKey="seeSuccessMessage"/> - - <!-- Unassign order status --> - <actionGroup ref="AdminGoToOrderStatusPageActionGroup" stepKey="goToOrderStatus"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForStatusPageLoad"/> - <actionGroup ref="FilterOrderStatusByLabelAndCodeActionGroup" stepKey="filterStatusGrid"> - <argument name="statusLabel" value="{{defaultOrderStatus.label}}"/> - <argument name="statusCode" value="{{defaultOrderStatus.status}}"/> - </actionGroup> - <click selector="{{AdminOrderStatusGridSection.unassign}}" stepKey="unassignOrderStatus"/> - <see selector="{{AdminMessagesSection.success}}" userInput="You have unassigned the order status." stepKey="seeMessage"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml index 42faab9dc3f07..21bc553507a04 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml @@ -74,14 +74,17 @@ <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <!-- Grab order id --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Open created order --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridById"/> + <comment userInput="BIC workaround" stepKey="clickCreatedOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder1"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> <!-- Go to invoice tab and fill data --> <actionGroup ref="AdminClickInvoiceButtonOrderViewActionGroup" stepKey="clickInvoiceAction"/> @@ -95,18 +98,19 @@ <!-- Assert invoice in invoices grid --> <actionGroup ref="FilterInvoiceGridByOrderIdWithCleanFiltersActionGroup" stepKey="filterInvoiceGridByOrderId"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <click selector="{{AdminInvoicesGridSection.firstRow}}" stepKey="opeCreatedInvoice"/> <waitForPageLoad stepKey="waitForInvoiceDetailsPageToLoad"/> <grabFromCurrentUrl regex="~/invoice_id/(\d+)/~" stepKey="grabInvoiceId"/> <!-- Assert invoice in invoices tab --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrders"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridByIdForAssertingInvoiceBtn"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridByIdForAssertingInvoiceBtn"/> + <comment userInput="BIC workaround" stepKey="clickOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder2"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickOrderInGrid"/> <click selector="{{AdminOrderDetailsMainActionsSection.invoiceTab}}" stepKey="clickInvoicesTabOrdersPage"/> <conditionalClick selector="{{AdminOrderInvoicesTabSection.clearFilters}}" dependentSelector="{{AdminOrderInvoicesTabSection.clearFilters}}" visible="true" stepKey="clearInvoiceFilters"/> <click selector="{{AdminOrderInvoicesTabSection.filters}}" stepKey="openOrderInvoicesGridFilters"/> @@ -118,7 +122,7 @@ <!-- Assert invoice items --> <actionGroup ref="FilterInvoiceGridByOrderIdWithCleanFiltersActionGroup" stepKey="filterInvoiceByOrderId"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <click selector="{{AdminInvoicesGridSection.firstRow}}" stepKey="openInvoice"/> <waitForPageLoad stepKey="waitForInvoicePageToLoad"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml index f8b171a587e09..4f20df24516eb 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml @@ -74,14 +74,17 @@ <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <!-- Grab order id --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Open created order --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridById"/> + <comment userInput="BIC workaround" stepKey="clickCreatedOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> <!-- Go to invoice tab and fill data --> <actionGroup ref="AdminClickInvoiceButtonOrderViewActionGroup" stepKey="clickInvoiceAction"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml index cfc42c98a5e71..457ee39f517ba 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml @@ -68,14 +68,17 @@ <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <!-- Grab order id --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Open created order --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridById"/> + <comment userInput="BIC workaround" stepKey="clickCreatedOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder1"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> <!-- Go to invoice tab and fill data --> <actionGroup ref="AdminClickInvoiceButtonOrderViewActionGroup" stepKey="clickInvoiceAction"/> @@ -93,18 +96,19 @@ <!-- Assert invoice in invoices grid --> <actionGroup ref="FilterInvoiceGridByOrderIdWithCleanFiltersActionGroup" stepKey="filterInvoiceGridByOrderId"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <click selector="{{AdminInvoicesGridSection.firstRow}}" stepKey="opeCreatedInvoice"/> <waitForPageLoad stepKey="waitForInvoiceDetailsPageToLoad"/> <grabFromCurrentUrl regex="~/invoice_id/(\d+)/~" stepKey="grabInvoiceId"/> <!-- Assert no invoice button --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrders"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridByIdForAssertingInvoiceBtn"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrders"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridByIdForAssertingInvoiceBtn"/> + <comment userInput="BIC workaround" stepKey="clickOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder2"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickOrderInGrid"/> <dontSeeElement selector="{{AdminOrderDetailsMainActionsSection.invoiceBtn}}" stepKey="dontSeeInvoiceBtn"/> <!-- Assert invoice in invoices tab --> @@ -136,18 +140,19 @@ <!-- Assert shipment in grid --> <actionGroup ref="FilterShipmentGridByOrderIdActionGroup" stepKey="filterShipmentGridByOrderId"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <click selector="{{AdminShipmentGridSection.firstRow}}" stepKey="openCreatedShipment"/> <waitForPageLoad stepKey="waitForShipmentDetailsPageToLoad"/> <grabFromCurrentUrl regex="~/shipment_id/(\d+)/~" stepKey="grabShipmentId"/> <!-- Assert no ship button --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToAdminOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridByIdForAssertingShipBtn"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToAdminOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrderGridByIdForAssertingShipBtn"/> + <comment userInput="BIC workaround" stepKey="selectOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder3"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="selectOrderInGrid"/> <dontSeeElement selector="{{AdminOrderDetailsMainActionsSection.shipBtn}}" stepKey="dontSeeShipBtn"/> <!-- Assert shipment in shipments tab --> @@ -163,7 +168,7 @@ <!-- Assert invoice items --> <actionGroup ref="FilterInvoiceGridByOrderIdWithCleanFiltersActionGroup" stepKey="filterInvoiceByOrderId"> - <argument name="orderId" value="$getOrderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <click selector="{{AdminInvoicesGridSection.firstRow}}" stepKey="openInvoice"/> <waitForPageLoad stepKey="waitForInvoicePageToLoad"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithZeroSubtotalCheckoutTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithZeroSubtotalCheckoutTest.xml index cd36547a877ec..fd4dc8e4aa422 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithZeroSubtotalCheckoutTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithZeroSubtotalCheckoutTest.xml @@ -83,14 +83,17 @@ <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> <!-- Grab order id --> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Open created order --> - <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="goToOrdersPage"/> - <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="orderId" value="$getOrderId"/> + <comment userInput="BIC workaround" stepKey="goToOrdersPage"/> + <comment userInput="BIC workaround" stepKey="filterOrdersGridById"/> + <comment userInput="BIC workaround" stepKey="clickCreatedOrderInGrid"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrder"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> - <click selector="{{AdminDataGridTableSection.firstRow}}" stepKey="clickCreatedOrderInGrid"/> <!-- Go to invoice tab and fill data --> <actionGroup ref="AdminClickInvoiceButtonOrderViewActionGroup" stepKey="clickInvoiceAction"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index 4992a474ef8a5..22cc18d6ad647 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -187,7 +187,8 @@ <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <!-- Filter and Open the customer edit page --> <actionGroup ref="AdminOpenCustomerEditPageActionGroup" stepKey="openCustomerEditPage1"> @@ -199,7 +200,7 @@ <click selector="{{AdminEditCustomerInformationSection.orders}}" stepKey="clickOnOrdersButton"/> <waitForPageLoad stepKey="waitForOrderPageToOpen"/> - <click selector="{{AdminEditCustomerOrdersSection.orderIdInGrid('$orderId')}}" stepKey="selectOnOrderID"/> + <click selector="{{AdminEditCustomerOrdersSection.orderIdInGrid('$orderNumber')}}" stepKey="selectOnOrderID"/> <!-- Assert ordered product in customer order section--> <waitForPageLoad stepKey="waitForOrderInformationToLoad"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml index 5e05e6b8a456d..d0bf3958179d4 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -71,9 +71,10 @@ <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderPendingStatus" after="seeSuccessMessage"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId" after="seeOrderPendingStatus"/> - <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="getOrderId"> - <actualResult type="const">$getOrderId</actualResult> + <comment userInput="BIC workaround" stepKey="getOrderId" after="seeOrderPendingStatus"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber" after="getOrderId"/> + <assertNotEmpty stepKey="assertOrderIdIsNotEmpty" after="orderNumber"> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation" after="assertOrderIdIsNotEmpty"> <argument name="customer" value="Simple_US_Customer"/> @@ -111,14 +112,14 @@ <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPageInvoice" after="clickSubmitInvoice"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeInvoiceCreateSuccess" after="seeViewOrderPageInvoice"/> <actionGroup ref="AssertAdminPageTitleActionGroup" stepKey="seePageNameMatchesOrderIdAfterInvoice" after="seeInvoiceCreateSuccess"> - <argument name="value" value="{$getOrderId}"/> + <argument name="value" value="{$orderNumber}"/> </actionGroup> <click selector="{{AdminOrderDetailsOrderViewSection.invoices}}" stepKey="clickOrderInvoicesTab" after="seePageNameMatchesOrderIdAfterInvoice"/> <waitForLoadingMaskToDisappear stepKey="waitForInvoiceGridLoadingMask" after="clickOrderInvoicesTab"/> <see selector="{{AdminOrderInvoicesTabSection.gridRow('1')}}" userInput="{{Simple_US_Customer.firstname}}" stepKey="seeOrderInvoiceInTabGrid" after="waitForInvoiceGridLoadingMask"/> <click selector="{{AdminOrderInvoicesTabSection.viewGridRow('1')}}" stepKey="clickToViewInvoiceRow" after="seeOrderInvoiceInTabGrid"/> - <see selector="{{AdminInvoiceOrderInformationSection.orderId}}" userInput="$getOrderId" stepKey="seeOrderIdOnInvoice" after="clickToViewInvoiceRow"/> + <see selector="{{AdminInvoiceOrderInformationSection.orderId}}" userInput="$orderNumber" stepKey="seeOrderIdOnInvoice" after="clickToViewInvoiceRow"/> <actionGroup ref="VerifyBasicInvoiceInformationActionGroup" stepKey="verifyBasicInvoiceInformation" after="seeOrderIdOnInvoice"> <argument name="customer" value="Simple_US_Customer"/> <argument name="shippingAddress" value="US_Address_TX"/> @@ -153,7 +154,7 @@ <see selector="{{AdminOrderCreditMemosTabSection.gridRow('1')}}" userInput="{{Simple_US_Customer.firstname}}" stepKey="seeOrderCreditMemoInTabGrid" after="waitForCreditMemoTabLoadingMask"/> <click selector="{{AdminOrderCreditMemosTabSection.viewGridRow('1')}}" stepKey="clickToViewCreditMemoRow" after="seeOrderCreditMemoInTabGrid"/> <waitForPageLoad stepKey="waitForCreditMemoPageLoad" after="clickToViewCreditMemoRow"/> - <see selector="{{AdminCreditMemoOrderInformationSection.orderId}}" userInput="$getOrderId" stepKey="seeOrderIdOnCreditMemo" after="waitForCreditMemoPageLoad"/> + <see selector="{{AdminCreditMemoOrderInformationSection.orderId}}" userInput="$orderNumber" stepKey="seeOrderIdOnCreditMemo" after="waitForCreditMemoPageLoad"/> <actionGroup ref="VerifyBasicCreditMemoInformationActionGroup" stepKey="verifyBasicCreditMemoInformation" after="seeOrderIdOnCreditMemo"> <argument name="customer" value="Simple_US_Customer"/> <argument name="shippingAddress" value="US_Address_TX"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml index 1164a87f1815a..3e97d91311935 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml @@ -104,6 +104,9 @@ <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <conditionalClick selector="{{AdminOrderStoreScopeTreeSection.storeOption(_defaultStore.name)}}" dependentSelector="{{AdminOrderStoreScopeTreeSection.storeOption(_defaultStore.name)}}" visible="true" stepKey="selectStoreViewIfAppears"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> <!-- Add configure to product --> <click selector="{{AdminCustomerActivitiesRecentlyViewedSection.addToOrderConfigure($$createConfigProduct.name$$)}}" stepKey="configureProduct"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontCustomerReorderProductWithCustomOptionsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontCustomerReorderProductWithCustomOptionsTest.xml index ed961095895d1..ee7eaeb4fdcf0 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontCustomerReorderProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontCustomerReorderProductWithCustomOptionsTest.xml @@ -108,6 +108,8 @@ <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openCart"/> <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <!-- Log out from storefront as Customer --> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogOut"/> @@ -116,8 +118,8 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdminForSubmitShipment"/> <!-- Open order --> - <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderForCreatingShipment"> - <argument name="orderId" value="{$grabOrderNumber}"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderForCreatingShipment"> + <argument name="entityId" value="{$orderId}"/> </actionGroup> <!-- Create Shipment for the order --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderFindByZipGuestTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderFindByZipGuestTest.xml index 0c55c1fdbd03a..6e825e3ed65ba 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderFindByZipGuestTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderFindByZipGuestTest.xml @@ -23,7 +23,7 @@ <!-- Fill the form with correspondent Order data using search by Zip --> <actionGroup ref="StorefrontFillOrdersAndReturnsFormTypeZipActionGroup" stepKey="fillOrderZip" before="clickContinue"> - <argument name="orderNumber" value="{$getOrderId}"/> + <argument name="orderNumber" value="{$orderNumber}"/> <argument name="customer" value="$$createCustomer$$"/> <argument name="address" value="US_Address_TX"/> </actionGroup> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml index a71dd0d512089..75208d6745589 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml @@ -16,9 +16,6 @@ <description value="Print Order from Guest on Frontend"/> <severity value="CRITICAL"/> <testCaseId value="MC-28494"/> - <skip> - <issueId value="MQE-2834" /> - </skip> <group value="sales"/> <group value="mtf_migrated"/> <group value="pr_exclude"/> @@ -122,7 +119,7 @@ </actionGroup> <grabTextFrom selector="{{AdminConfigurableProductFormSection.currentAttribute}}" stepKey="grabAttribute"/> <assertNotEmpty stepKey="assertNotEmpty"> - <actualResult type="const">$grabAttribute</actualResult> + <actualResult type="const">$grabAttribute</actualResult> </assertNotEmpty> <!-- Create bundle Product --> @@ -157,7 +154,7 @@ </actionGroup> <grabTextFrom selector="{{AdminProductFormBundleSection.currentBundleOption}}" stepKey="grabBundleOption"/> <assertNotEmpty stepKey="assertBundleOptionNotEmpty"> - <actualResult type="const">$grabBundleOption</actualResult> + <actualResult type="const">$grabBundleOption</actualResult> </assertNotEmpty> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductGridFilters"/> @@ -247,9 +244,11 @@ <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> - <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId"/> + <comment userInput="BIC workaround" stepKey="getOrderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> <assertNotEmpty stepKey="assertOrderIdIsNotEmpty"> - <actualResult type="const">$getOrderId</actualResult> + <actualResult type="const">$orderNumber</actualResult> </assertNotEmpty> <!-- Find the Order on frontend > Navigate to: Orders and Returns --> @@ -258,7 +257,7 @@ <!-- Fill the form with correspondent Order data --> <actionGroup ref="StorefrontFillOrdersAndReturnsFormActionGroup" stepKey="fillOrder"> - <argument name="orderNumber" value="{$getOrderId}"/> + <argument name="orderNumber" value="{$orderNumber}"/> <argument name="customer" value="$createCustomer$"/> </actionGroup> @@ -268,12 +267,17 @@ <!-- Click on the "Print Order" button --> <click selector="{{StorefrontGuestOrderViewSection.printOrder}}" stepKey="printOrder"/> - <waitForPageLoad stepKey="waitForPrintWindowToOpen" /> - <switchToWindow stepKey="switchToWindow"/> - <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="waitForPrintTabToOpen"/> - <switchToNextTab stepKey="switchToTab"/> - <waitForPageLoad stepKey="waitForPrintPreviewToLoad"/> - <seeInCurrentUrl url="sales/guest/print/order_id/" stepKey="seePrintPage"/> + <helper class="Magento\Sales\Test\Mftf\Helper\SalesHelper" method="switchToWindowWithUrlAndClosePrintDialogIfEncountered" stepKey="switchToWindowWithUrlAndClosePrintDialogIfEncountered"> + <argument name="expectedUrl">sales/guest/print/order_id/</argument> + <argument name="expectedUrlComparisonType">COMPARISON_PATH_SUBSET_MATCH</argument> + </helper> + + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForPrintWindowToOpen" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToWindow"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForPrintTabToOpen"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToTab"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForPrintPreviewToLoad"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="seePrintPage"/> <!-- AssertSalesPrintOrderProducts --> <see userInput="$createBundleProduct.name$" selector="{{StorefrontOrderDetailsSection.productNameCell}}" stepKey="seeBundleProduct"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml index f5d648598c8b0..54432b8699337 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml @@ -16,9 +16,6 @@ <description value="Check while order printing URL with an id of not relevant order redirects to order history"/> <severity value="MAJOR"/> <testCaseId value="MC-28543"/> - <skip> - <issueId value="MQE-2834" /> - </skip> <group value="sales"/> <group value="pr_exclude"/> </annotations> @@ -37,59 +34,70 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> </after> - <!--Log in to Storefront as Customer 1 --> + <!-- Log in to Storefront as Customer 1 --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> <argument name="Customer" value="$createCustomer$"/> </actionGroup> - <!--Create an order at Storefront as Customer 1 --> + <!-- Create an order on Storefront as Customer 1 and open print order page --> <actionGroup ref="CreateOrderToPrintPageWithSelectedPaymentMethodActionGroup" stepKey="createOrderToPrint"> <argument name="Category" value="$createCategory$"/> </actionGroup> - <!--Go to 'print order' page by grabbed order id--> + <!-- Go to 'print order' page by grabbed order id and assert browser does not redirect to order history page --> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderIdFromURL"/> <comment userInput="BIC workaround" stepKey="waitForPrintWindowToOpen"/> - <switchToWindow stepKey="switchToPrintPage"/> - <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage"/> - <openNewTab stepKey="openNewTab"/> - <waitForPageLoad stepKey="waitForNewTabToOpen" /> - <switchToNextTab stepKey="switchForward"/> - <waitForElement selector="body" stepKey="waitForNewTab3HTML" /> - <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage"/> - <waitForPageLoad stepKey="waitForDuplicatePrintWindowToOpen" /> - <switchToWindow stepKey="switchToDuplicatePrintPage"/> - <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkDuplicatePrintPage"/> + <helper class="Magento\Sales\Test\Mftf\Helper\SalesHelper" method="switchToWindowWithUrlAndClosePrintDialogIfEncountered" stepKey="switchToWindowWithUrlAndClosePrintDialogIfEncountered"> + <argument name="expectedUrl">sales/order/print/order_id/{$grabOrderIdFromURL}</argument> + <argument name="expectedUrlComparisonType">COMPARISON_PATH_SUBSET_MATCH</argument> + </helper> - <!--Log out as customer 1--> - <openNewTab stepKey="openNewTab2"/> - <waitForPageLoad stepKey="waitForNewTabToOpen1" /> - <switchToNextTab stepKey="switchForward2"/> - <waitForElement selector="body" stepKey="waitForNewTab2HTML" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToPrintPage"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="checkPrintPage"/> + + <comment userInput="Step key preserved for backwards compatibility" stepKey="openNewTab"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForNewTabToOpen" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchForward"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForNewTab3HTML" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="duplicatePrintPage"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForDuplicatePrintWindowToOpen" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToDuplicatePrintPage"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="checkDuplicatePrintPage"/> + + <!-- Log out of customer 1's account --> + <comment userInput="Step key preserved for backwards compatibility" stepKey="openNewTab2"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForNewTabToOpen1" /> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchForward2"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForNewTab2HTML" /> <amOnPage url="{{StorefrontCustomerSignOutPage.url}}" stepKey="signOut"/> <waitForLoadingMaskToDisappear stepKey="waitSignOutPage"/> - <!--Log in to Storefront as Customer 2 --> + <!-- Log in to Storefront as Customer 2 --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp2"> <argument name="Customer" value="$createCustomer2$"/> </actionGroup> - <!--Create an order at Storefront as Customer 2 --> + <!-- Create an order on Storefront as Customer 2 and open print order page --> <actionGroup ref="CreateOrderToPrintPageWithSelectedPaymentMethodActionGroup" stepKey="createOrderToPrint2"> <argument name="Category" value="$createCategory$"/> </actionGroup> - <!--Try to load 'print order' page with not relevant order id to be redirected to 'order history' page--> - <waitForPageLoad stepKey="waitForPrintWindowToOpen2" /> - <switchToWindow stepKey="switchToPrintPage2"/> - <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage2"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="waitForPrintWindowToOpen2" /> + <helper class="Magento\Sales\Test\Mftf\Helper\SalesHelper" method="switchToWindowWithUrlAndClosePrintDialogIfEncountered" stepKey="switchToWindowWithUrlAndClosePrintDialogIfEncountered2"> + <argument name="expectedUrl">sales/order/print/order_id/</argument> + <argument name="expectedUrlComparisonType">COMPARISON_PATH_SUBSET_MATCH</argument> + </helper> + <comment userInput="Step key preserved for backwards compatibility" stepKey="switchToPrintPage2"/> + <comment userInput="Step key preserved for backwards compatibility" stepKey="checkPrintPage2"/> <openNewTab stepKey="openNewTab3"/> <waitForPageLoad stepKey="waitForNewTabToOpen2" /> <switchToNextTab stepKey="switchForward4"/> <waitForElement selector="body" stepKey="waitForNewTabHTML" /> + + <!-- Assert trying to load 'print order' page as customer 2 with the the order id created by customer 1 will redirect to 'order history' page --> <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage2"/> <waitForPageLoad stepKey="waitForOpenDuplicatePage" /> <seeElement selector="{{StorefrontCustomerOrderSection.isMyOrdersSection}}" stepKey="waitOrderHistoryPage"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml index cf4e408282565..0c617bce9e7bd 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml @@ -73,11 +73,12 @@ <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> - <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="grabOrderNumber"/> + <comment userInput="BIC workaround" stepKey="grabOrderNumber"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="openOrderViewPage"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> + <!--Step11. Go to admin Order page for newly created order--> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="filterOrdersGridById"> - <argument name="entityId" value="$grabOrderId"/> + <argument name="entityId" value="{$grabOrderId}"/> </actionGroup> <actionGroup ref="GoToShipmentIntoOrderActionGroup" stepKey="clickShipAction"/> @@ -85,7 +86,7 @@ <actionGroup ref="StorefrontNavigateToCustomerOrdersHistoryPageActionGroup" stepKey="goToOrderHistoryPage"/> <actionGroup ref="StorefrontOpenOrderShipmentsTabByOrderIdActionGroup" stepKey="amOnOrderShipmentPage"> <argument name="orderId" value="$grabOrderId"/> - </actionGroup> + </actionGroup> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForOrderShipmentsPageLoad"/> <actionGroup ref="AssertStorefrontOrderShipmentsQtyShippedActionGroup" stepKey="verifyAssertOrderShipments"> <argument name="expectedQtyShipped" value="2.14"/> diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index 36b9255a04c2f..ddf7193437af6 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/giftmessage.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/giftmessage.js index 907a1a0e3341a..e309c0fecab05 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/giftmessage.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/giftmessage.js @@ -248,7 +248,7 @@ define([ return false; } - if (jQuery.isFunction(giftOptionsForm[0].reset)) { + if (typeof (giftOptionsForm[0].reset) === 'function') { giftOptionsForm[0].reset(); } this.closeWindow(); diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json index 10b00295f79a0..e430d5043033c 100644 --- a/app/code/Magento/SalesAnalytics/composer.json +++ b/app/code/Magento/SalesAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/SalesGraphQl/composer.json b/app/code/Magento/SalesGraphQl/composer.json index 84daf7a0506db..eb914d4c2ee6d 100644 --- a/app/code/Magento/SalesGraphQl/composer.json +++ b/app/code/Magento/SalesGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-store": "*", diff --git a/app/code/Magento/SalesGraphQl/etc/schema.graphqls b/app/code/Magento/SalesGraphQl/etc/schema.graphqls index 288f648b50ee9..e6eb8efa294ef 100644 --- a/app/code/Magento/SalesGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SalesGraphQl/etc/schema.graphqls @@ -69,7 +69,7 @@ type OrderAddress @doc(description: "Contains detailed information about an orde country_code: CountryCodeEnum @doc(description: "The customer's country.") street: [String!]! @doc(description: "An array of strings that define the street number and name.") company: String @doc(description: "The customer's company.") - telephone: String! @doc(description: "The telephone number.") + telephone: String @doc(description: "The telephone number.") fax: String @doc(description: "The fax number.") postcode: String @doc(description: "The customer's ZIP or postal code.") city: String! @doc(description: "The city or town.") diff --git a/app/code/Magento/SalesInventory/composer.json b/app/code/Magento/SalesInventory/composer.json index 3575f60006c6a..b67cf306db866 100644 --- a/app/code/Magento/SalesInventory/composer.json +++ b/app/code/Magento/SalesInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Actions.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Actions.php index 7e8e3c29b0b0f..5f6b3fad39b55 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Actions.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Actions.php @@ -5,6 +5,7 @@ */ namespace Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab; +use Magento\Backend\Block\Widget\Form\Renderer\Fieldset; use Magento\Framework\App\ObjectManager; class Actions extends \Magento\Backend\Block\Widget\Form\Generic implements @@ -69,7 +70,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabClass() { @@ -77,7 +78,7 @@ public function getTabClass() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabUrl() { @@ -85,7 +86,7 @@ public function getTabUrl() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAjaxLoaded() { @@ -93,7 +94,7 @@ public function isAjaxLoaded() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabLabel() { @@ -101,7 +102,7 @@ public function getTabLabel() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabTitle() { @@ -109,7 +110,7 @@ public function getTabTitle() } /** - * {@inheritdoc} + * @inheritdoc */ public function canShowTab() { @@ -117,7 +118,7 @@ public function canShowTab() } /** - * {@inheritdoc} + * @inheritdoc */ public function isHidden() { @@ -165,7 +166,9 @@ protected function addTabToForm($model, $fieldsetId = 'actions_fieldset', $formN /** @var \Magento\Framework\Data\Form $form */ $form = $this->_formFactory->create(); $form->setHtmlIdPrefix('rule_'); - $renderer = $this->_rendererFieldset->setTemplate( + + $renderer = $this->getLayout()->createBlock(Fieldset::class); + $renderer->setTemplate( 'Magento_CatalogRule::promo/fieldset.phtml' )->setNewChildUrl( $newChildUrl diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php index ff905bf5cb9ff..e1ed56f4b32ff 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php @@ -8,7 +8,7 @@ namespace Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Data\Form\Element\Fieldset; +use Magento\Backend\Block\Widget\Form\Renderer\Fieldset; use Magento\SalesRule\Model\Rule; /** @@ -163,7 +163,9 @@ protected function addTabToForm($model, $fieldsetId = 'conditions_fieldset', $fo /** @var \Magento\Framework\Data\Form $form */ $form = $this->_formFactory->create(); $form->setHtmlIdPrefix('rule_'); - $renderer = $this->_rendererFieldset->setTemplate( + + $renderer = $this->getLayout()->createBlock(Fieldset::class); + $renderer->setTemplate( 'Magento_CatalogRule::promo/fieldset.phtml' )->setNewChildUrl( $newChildUrl diff --git a/app/code/Magento/SalesRule/Helper/CartFixedDiscount.php b/app/code/Magento/SalesRule/Helper/CartFixedDiscount.php index 025f2c7215964..e3ad412a8e783 100644 --- a/app/code/Magento/SalesRule/Helper/CartFixedDiscount.php +++ b/app/code/Magento/SalesRule/Helper/CartFixedDiscount.php @@ -51,6 +51,8 @@ public function calculateShippingAmountWhenAppliedToShipping( ): float { $shippingAmount = (float) $address->getShippingAmount(); if ($shippingAmount == 0.0) { + $addressQty = $this->getAddressQty($address); + $address->setItemQty($addressQty); $address->setCollectShippingRates(true); $address->collectShippingRates(); $shippingRates = $address->getAllShippingRates(); @@ -82,7 +84,7 @@ public function getDiscountAmount( float $baseRuleTotals, string $discountType ): float { - $ratio = $baseItemPrice * $qty / $baseRuleTotals; + $ratio = $baseRuleTotals != 0 ? $baseItemPrice * $qty / $baseRuleTotals : 0; return $this->deltaPriceRound->round( $ruleDiscount * $ratio, $discountType @@ -109,7 +111,7 @@ public function getDiscountedAmountProportionally( string $discountType ): float { $baseItemPriceTotal = $baseItemPrice * $qty - $baseItemDiscountAmount; - $ratio = $baseItemPriceTotal / $baseRuleTotalsDiscount; + $ratio = $baseRuleTotalsDiscount != 0 ? $baseItemPriceTotal / $baseRuleTotalsDiscount : 0; $discountAmount = $this->deltaPriceRound->round($ruleDiscount * $ratio, $discountType); return $discountAmount; } @@ -127,7 +129,7 @@ public function getShippingDiscountAmount( float $shippingAmount, float $quoteBaseSubtotal ): float { - $ratio = $shippingAmount / $quoteBaseSubtotal; + $ratio = $quoteBaseSubtotal != 0 ? $shippingAmount / $quoteBaseSubtotal : 0; return $this->priceCurrency ->roundPrice( $rule->getDiscountAmount() * $ratio @@ -241,4 +243,35 @@ public function getAvailableDiscountAmount( } return $availableDiscountAmount; } + + /** + * Get address quantity. + * + * @param AddressInterface $address + * @return float + */ + private function getAddressQty(AddressInterface $address): float + { + $addressQty = 0; + $items = array_filter( + $address->getAllItems(), + function ($item) { + return !$item->getProduct()->isVirtual() && !$item->getParentItem(); + } + ); + foreach ($items as $item) { + if ($item->getHasChildren() && $item->isShipSeparately()) { + foreach ($item->getChildren() as $child) { + if ($child->getProduct()->isVirtual()) { + continue; + } + $addressQty += $child->getTotalQty(); + } + } else { + $addressQty += (float)$item->getQty(); + } + } + + return (float)$addressQty; + } } diff --git a/app/code/Magento/SalesRule/Model/Quote/Discount.php b/app/code/Magento/SalesRule/Model/Quote/Discount.php index 0663e57a0a0f8..111ceed74afd9 100644 --- a/app/code/Magento/SalesRule/Model/Quote/Discount.php +++ b/app/code/Magento/SalesRule/Model/Quote/Discount.php @@ -33,7 +33,7 @@ */ class Discount extends AbstractTotal { - const COLLECTOR_TYPE_CODE = 'discount'; + public const COLLECTOR_TYPE_CODE = 'discount'; /** * Discount calculation object @@ -293,7 +293,7 @@ public function fetch(Quote $quote, Total $total) $amount = $total->getDiscountAmount(); if ($amount != 0) { - $description = $total->getDiscountDescription(); + $description = $total->getDiscountDescription() ?? ''; $result = [ 'code' => $this->getCode(), 'title' => strlen($description) ? __('Discount (%1)', $description) : __('Discount'), diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Report/Rule/Createdat.php b/app/code/Magento/SalesRule/Model/ResourceModel/Report/Rule/Createdat.php index 4ebf01d145fe9..342fa8363da09 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Report/Rule/Createdat.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Report/Rule/Createdat.php @@ -89,10 +89,13 @@ protected function _aggregateByOrder($aggregationField, $from, $to) 0 ), 'total_amount' => $connection->getIfNullSql( - 'SUM((base_subtotal - ' . $connection->getIfNullSql( + 'SUM(((base_subtotal - ' . $connection->getIfNullSql( 'base_subtotal_canceled', 0 - ) . ' - ' . $connection->getIfNullSql( + ) . ' + ' . $connection->getIfNullSql( + 'base_shipping_amount - ' . $connection->getIfNullSql('base_shipping_canceled', 0), + 0 + ) . ') - ' . $connection->getIfNullSql( 'ABS(base_discount_amount) - ABS(' . $connection->getIfNullSql('base_discount_canceled', 0) . ')', 0 @@ -119,17 +122,20 @@ protected function _aggregateByOrder($aggregationField, $from, $to) 0 ), 'total_amount_actual' => $connection->getIfNullSql( - 'SUM((base_subtotal_invoiced - ' . $connection->getIfNullSql( + 'SUM(((base_subtotal_invoiced - ' . $connection->getIfNullSql( 'base_subtotal_refunded', 0 - ) . ' - ' . $connection->getIfNullSql( + ) . ' + ' . $connection->getIfNullSql( + 'base_shipping_invoiced - ' . $connection->getIfNullSql('base_shipping_refunded', 0), + 0 + ) . ') - ' . $connection->getIfNullSql( 'ABS(base_discount_invoiced) - ABS(' . $connection->getIfNullSql('base_discount_refunded', 0) . ')', 0 ) . ' + ' . $connection->getIfNullSql( 'base_tax_invoiced - ' . $connection->getIfNullSql('base_tax_refunded', 0), 0 - ) . ') * base_to_global_rate)', + ) . ') * base_to_global_rate)', 0 ), ]; diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StoreFrontAddZeroPriceProductToCardWithFixedAmountPriceRule.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StoreFrontAddZeroPriceProductToCardWithFixedAmountPriceRule.xml new file mode 100644 index 0000000000000..9bcefdcfc3144 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StoreFrontAddZeroPriceProductToCardWithFixedAmountPriceRule.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontAddZeroPriceProductToCardWithFixedAmountPriceRule"> + <annotations> + <features value="SalesRule"/> + <stories value="Zero price product added to cart while cart price rule is Fixed amount discount for whole cart"/> + <title value="Add zero price product to cart when fixed amount discount for the whole cart rule is active"/> + <description value="Customers should be able to add a zero price product from the storefront when the cart price rule of type Fixed amount discount for the whole cart is configured"/> + <severity value="MAJOR"/> + <testCaseId value="AC-1618"/> + <useCaseId value="ACP2E-285"/> + <group value="SalesRule"/> + </annotations> + + <before> + <!-- Create Simple Product and Category --> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="SimpleProduct_zero" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + <!-- Create cart price rule --> + <actionGroup ref="AdminCreateCartPriceRuleActionGroup" stepKey="createCartPriceRule"> + <argument name="ruleName" value="PriceRuleWithCondition"/> + </actionGroup> + </before> + + <after> + <!-- Delete the cart price rule we made during the test --> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{PriceRuleWithCondition.name}}"/> + </actionGroup> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createPreReqCategory" stepKey="deleteCategory"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + </after> + + <!-- Go to the storefront and add the product to the cart --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="gotoAndAddProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!-- Go to the cart page and verify we see the product --> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="gotoCart"/> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + <argument name="productPrice" value="$$createSimpleProduct.price$$"/> + <argument name="subtotal" value="$$createSimpleProduct.price$$" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartRuleCouponForFreeShippingTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartRuleCouponForFreeShippingTest.xml index 7d266e2cdd6f1..b46436c92b54b 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartRuleCouponForFreeShippingTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartRuleCouponForFreeShippingTest.xml @@ -89,8 +89,10 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Go to created Order --> - <amOnPage url="{{AdminOrderPage.url({$grabOrderId})}}" stepKey="goToAdminViewOrder"/> - <waitForPageLoad stepKey="waitForOrderPage"/> + <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="goToAdminViewOrder"> + <argument name="entityId" value="{$grabOrderId}"/> + </actionGroup> + <comment userInput="BIC workaround" stepKey="waitForOrderPage"/> <!-- Assert Coupon Code will shown in Shipping total description --> <actionGroup ref="AssertAdminShippingDescriptionInOrderViewActionGroup" stepKey="seeCouponInShippingDescription"> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml index b4f4c157bbd44..ec36c7a558f1d 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml @@ -61,6 +61,8 @@ </before> <after> <!-- Removed created Data --> + <deleteData createDataKey="initialTaxRule" stepKey="deleteTaxRule"/> + <deleteData createDataKey="initialTaxRate" stepKey="deleteTaxRate"/> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRulesPage"/> <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> <argument name="name" value="SampleRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontZeroPriceProductWithDiscountUsingCartPriceRuleTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontZeroPriceProductWithDiscountUsingCartPriceRuleTest.xml index eb1e4efbec2d6..be6b4231810b5 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontZeroPriceProductWithDiscountUsingCartPriceRuleTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontZeroPriceProductWithDiscountUsingCartPriceRuleTest.xml @@ -80,8 +80,8 @@ <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> <!-- Navigate to admin order details page --> - <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderById"> - <argument name="entityId" value="$grabOrderNumber"/> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> </actionGroup> <!-- Assert Discount amount in admin --> diff --git a/app/code/Magento/SalesRule/composer.json b/app/code/Magento/SalesRule/composer.json index 910777aad9739..7a8c149e43290 100644 --- a/app/code/Magento/SalesRule/composer.json +++ b/app/code/Magento/SalesRule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/SalesSequence/composer.json b/app/code/Magento/SalesSequence/composer.json index d4382ec42c060..140cbf0aa106d 100644 --- a/app/code/Magento/SalesSequence/composer.json +++ b/app/code/Magento/SalesSequence/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/SampleData/composer.json b/app/code/Magento/SampleData/composer.json index 4b45599d4266a..d715a9f956798 100644 --- a/app/code/Magento/SampleData/composer.json +++ b/app/code/Magento/SampleData/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/Search/Test/Mftf/Test/AdminGlobalSearchOnProductPageTest.xml b/app/code/Magento/Search/Test/Mftf/Test/AdminGlobalSearchOnProductPageTest.xml index 189b8962957a2..09d1f5ae50d1c 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/AdminGlobalSearchOnProductPageTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/AdminGlobalSearchOnProductPageTest.xml @@ -29,9 +29,7 @@ </actionGroup> <!-- Delete category --> - <actionGroup ref="DeleteCategoryActionGroup" stepKey="deleteCreatedNewRootCategory"> - <argument name="categoryEntity" value="_defaultCategory"/> - </actionGroup> + <comment userInput="BIC workaround" stepKey="deleteCreatedNewRootCategory"/> <!-- Logout --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> @@ -46,10 +44,7 @@ <argument name="product" value="SimpleProduct"/> </actionGroup> - <!-- Create new category for product --> - <actionGroup ref="FillNewProductCategoryActionGroup" stepKey="FillNewProductCategory"> - <argument name="categoryName" value="{{_defaultCategory.name}}"/> - </actionGroup> + <comment userInput="BIC workaround" stepKey="FillNewProductCategory"/> <!-- Save product form --> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml index dda47677ccfca..ed026a5cfd205 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -26,8 +26,12 @@ <!-- Create product with description --> <createData entity="SimpleProductWithDescription" stepKey="simpleProduct"/> - <comment userInput="Adding the comment to replace CliIndexerReindexActionGroup action group ('indexer:reindex' commands) for preserving Backward Compatibility" stepKey="reindex"/> - <comment userInput="Adding the comment to replace CliCacheFlushActionGroup action group ('cache:flush' command) for preserving Backward Compatibility" stepKey="flushCache"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="flushCache"> + <argument name="tags" value="full_page"/> + </actionGroup> </before> <after> <!-- Delete created product --> diff --git a/app/code/Magento/Search/composer.json b/app/code/Magento/Search/composer.json index 39a6b0a6b2d3d..83290e183a132 100644 --- a/app/code/Magento/Search/composer.json +++ b/app/code/Magento/Search/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog-search": "*", diff --git a/app/code/Magento/Security/composer.json b/app/code/Magento/Security/composer.json index b6663a27d3298..d1c697efb54b5 100644 --- a/app/code/Magento/Security/composer.json +++ b/app/code/Magento/Security/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index 99d53a15b71c3..87a2f671b312a 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/SendFriendGraphQl/composer.json b/app/code/Magento/SendFriendGraphQl/composer.json index fda17c2fde8bc..8ed7c9e3e37c4 100644 --- a/app/code/Magento/SendFriendGraphQl/composer.json +++ b/app/code/Magento/SendFriendGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-send-friend": "*", diff --git a/app/code/Magento/Shipping/Model/Simplexml/Element.php b/app/code/Magento/Shipping/Model/Simplexml/Element.php index a0ca9caf1d659..878a875c969be 100644 --- a/app/code/Magento/Shipping/Model/Simplexml/Element.php +++ b/app/code/Magento/Shipping/Model/Simplexml/Element.php @@ -20,6 +20,7 @@ class Element extends \Magento\Framework\Simplexml\Element * @param string $namespace If specified, the namespace to which the attribute belongs. * @return void */ + #[\ReturnTypeWillChange] public function addAttribute($name, $value = null, $namespace = null) { $value = $value !== null ? $this->xmlentities($value) : ''; @@ -34,6 +35,7 @@ public function addAttribute($name, $value = null, $namespace = null) * @param string $namespace If specified, the namespace to which the child element belongs. * @return \Magento\Shipping\Model\Simplexml\Element */ + #[\ReturnTypeWillChange] public function addChild($name, $value = null, $namespace = null) { if ($value !== null) { diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckTheConfirmationPopupTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckTheConfirmationPopupTest.xml index fea499954896d..6ec3f3eeeb347 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckTheConfirmationPopupTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCheckTheConfirmationPopupTest.xml @@ -30,10 +30,11 @@ <argument name="customer" value="$$createCustomer$$"/> <argument name="product" value="$$createSimpleProduct$$"/> </actionGroup> - <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderNumber"/> <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="createShipmentForOrder"/> <actionGroup ref="FilterShipmentGridByOrderIdActionGroup" stepKey="filterForNewlyCreatedShipment"> - <argument name="orderId" value="$orderId"/> + <argument name="orderId" value="$orderNumber"/> </actionGroup> <actionGroup ref="AdminSelectFirstGridRowActionGroup" stepKey="selectShipmentFromGrid"/> <actionGroup ref="AdminAddTrackingNumberToShipmentActionGroup" stepKey="addTrackingNumber"> diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index b781430d84861..8a4ba0ad3dd74 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-gd": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sitemap/Test/Mftf/Data/SitemapData.xml b/app/code/Magento/Sitemap/Test/Mftf/Data/SitemapData.xml index b952f829b1d96..3e2596cf27a3e 100644 --- a/app/code/Magento/Sitemap/Test/Mftf/Data/SitemapData.xml +++ b/app/code/Magento/Sitemap/Test/Mftf/Data/SitemapData.xml @@ -16,4 +16,8 @@ <data key="filename" unique="prefix">sitemap.xml</data> <data key="path">/</data> </entity> + <entity name="UniqueSitemapNameMediaPath"> + <data key="filename" unique="prefix">sitemap.xml</data> + <data key="path">/media/sitemap/</data> + </entity> </entities> diff --git a/app/code/Magento/Sitemap/Test/Mftf/Test/StorefrontSitemapUseCanonicalUrlProductTest.xml b/app/code/Magento/Sitemap/Test/Mftf/Test/StorefrontSitemapUseCanonicalUrlProductTest.xml index e114ac436de4c..4125f3c82e6ec 100644 --- a/app/code/Magento/Sitemap/Test/Mftf/Test/StorefrontSitemapUseCanonicalUrlProductTest.xml +++ b/app/code/Magento/Sitemap/Test/Mftf/Test/StorefrontSitemapUseCanonicalUrlProductTest.xml @@ -34,7 +34,7 @@ </actionGroup> <actionGroup ref="AdminMarketingSiteMapNavigateNewActionGroup" stepKey="navigateToNewSitemapPage"/> <actionGroup ref="AdminMarketingSiteMapFillFormSaveAndGenerateActionGroup" stepKey="createAndGenerateSitemap"> - <argument name="sitemap" value="UniqueSitemapName"/> + <argument name="sitemap" value="UniqueSitemapNameMediaPath"/> </actionGroup> </before> <after> diff --git a/app/code/Magento/Sitemap/composer.json b/app/code/Magento/Sitemap/composer.json index 7e995e4153377..ae7b2f5b6925a 100644 --- a/app/code/Magento/Sitemap/composer.json +++ b/app/code/Magento/Sitemap/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoreViewsActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoreViewsActionGroup.xml new file mode 100644 index 0000000000000..4947b446583ab --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoreViewsActionGroup.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteMultipleStoreViewsActionGroup"> + <annotations> + <description>Deletes all specified store views one by one on the 'Stores' page. Defaults to delete all store + views except the default 'Default Store View'.</description> + </annotations> + <arguments> + <argument name="storeViewsToDelete" defaultValue="{{AdminStoresMainActionsSection.allNonDefaultStoreViews}}" type="string"/> + <argument name="expectedRemainingStoreViews" defaultValue="1" type="string"/> + </arguments> + <waitForElementVisible selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="waitForResetFilter"/> + <click selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="clickResetFilter"/> + <waitForPageLoad stepKey="waitForGridReset"/> + <helper class="Magento\Store\Test\Mftf\Helper\StoreHelpers" method="deleteAllSpecifiedStoreViews" stepKey="deleteAllSpecifiedStoreViews"> + <argument name="storeViewsToDelete">{{storeViewsToDelete}}</argument> + <argument name="deleteButton">{{AdminMainActionsSection.delete}}</argument> + <argument name="createDbBackupButton">{{AdminStoresDeleteStoreGroupSection.createDbBackup}}</argument> + <argument name="successMessage">You deleted the store view.</argument> + <argument name="successMessageContainer">{{AdminMessagesSection.success}}</argument> + </helper> + <waitForPageLoad stepKey="waitForGridLoad"/> + <seeNumberOfElements userInput="{{expectedRemainingStoreViews}}" selector="{{AdminStoresMainActionsSection.allStoreViews}}" stepKey="seeExpectedFinalStoreViews"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoresActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoresActionGroup.xml new file mode 100644 index 0000000000000..c82fc0990ae08 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleStoresActionGroup.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteMultipleStoresActionGroup"> + <annotations> + <description>Deletes all specified stores one by one on the 'Stores' page. Defaults to delete all stores + except the default 'Main Website Store'.</description> + </annotations> + <arguments> + <argument name="storesToDelete" defaultValue="{{AdminStoresMainActionsSection.allNonDefaultStores}}" type="string"/> + <argument name="expectedRemainingStores" defaultValue="1" type="string"/> + </arguments> + <waitForElementVisible selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="waitForResetFilter"/> + <click selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="clickResetFilter"/> + <waitForPageLoad stepKey="waitForGridReset"/> + <helper class="Magento\Store\Test\Mftf\Helper\StoreHelpers" method="deleteAllSpecifiedStores" stepKey="deleteAllSpecifiedStores"> + <argument name="storesToDelete">{{storesToDelete}}</argument> + <argument name="deleteButton">{{AdminMainActionsSection.delete}}</argument> + <argument name="createDbBackupButton">{{AdminStoresDeleteStoreGroupSection.createDbBackup}}</argument> + <argument name="successMessage">You deleted the store.</argument> + <argument name="successMessageContainer">{{AdminMessagesSection.success}}</argument> + </helper> + <waitForPageLoad stepKey="waitForGridLoad"/> + <seeNumberOfElements userInput="{{expectedRemainingStores}}" selector="{{AdminStoresMainActionsSection.allStores}}" stepKey="seeExpectedFinalStores"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleWebsitesActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleWebsitesActionGroup.xml new file mode 100644 index 0000000000000..428d2899871b9 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteMultipleWebsitesActionGroup.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteMultipleWebsitesActionGroup"> + <annotations> + <description>Deletes all specified websites one by one on the 'Stores' page. Defaults to delete all websites + except the default 'Main Website'.</description> + </annotations> + <arguments> + <argument name="websitesToDelete" defaultValue="{{AdminStoresMainActionsSection.allNonDefaultWebsites}}" type="string"/> + <argument name="expectedRemainingWebsites" defaultValue="1" type="string"/> + </arguments> + <waitForElementVisible selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="waitForResetFilter"/> + <click selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="clickResetFilter"/> + <waitForPageLoad stepKey="waitForGridReset"/> + <helper class="Magento\Store\Test\Mftf\Helper\StoreHelpers" method="deleteAllSpecifiedWebsites" stepKey="deleteAllSpecifiedWebsites"> + <argument name="websitesToDelete">{{websitesToDelete}}</argument> + <argument name="deleteButton">{{AdminMainActionsSection.delete}}</argument> + <argument name="createDbBackupButton">{{AdminStoresDeleteStoreGroupSection.createDbBackup}}</argument> + <argument name="successMessage">You deleted the website.</argument> + <argument name="successMessageContainer">{{AdminMessagesSection.success}}</argument> + </helper> + <waitForPageLoad stepKey="waitForGridLoad"/> + <seeNumberOfElements userInput="{{expectedRemainingWebsites}}" selector="{{AdminStoresMainActionsSection.allWebsites}}" stepKey="seeExpectedFinalWebsites"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreBackupEnabledYesActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreBackupEnabledYesActionGroup.xml index 97fb6a83ab06f..f3d74410cbc95 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreBackupEnabledYesActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreBackupEnabledYesActionGroup.xml @@ -20,12 +20,15 @@ <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> <fillField userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="fillSearchStoreGroupField"/> <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> - <see userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="verifyThatCorrectStoreGroupFound"/> + <waitForText userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="verifyThatCorrectStoreGroupFound"/> <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clickEditExistingStoreRow"/> <waitForPageLoad stepKey="waitForStoreToLoad"/> <click selector="{{AdminStoresMainActionsSection.deleteButton}}" stepKey="clickDeleteStoreGroupButtonOnEditStorePage"/> + <waitForPageLoad stepKey="waitForDeletePageToLoad"/> <selectOption userInput="Yes" selector="{{AdminStoresDeleteStoreGroupSection.createDbBackup}}" stepKey="setCreateDbBackupToNo"/> <click selector="{{AdminStoresDeleteStoreGroupSection.deleteStoreGroupButton}}" stepKey="clickDeleteStoreGroupButtonOnDeleteStorePage"/> + <waitForPageLoad stepKey="waitForDelete"/> + <waitForElement selector="{{AdminStoresGridSection.successMessage}}" stepKey="waitForSuccessMessages"/> <see selector="{{AdminStoresGridSection.successMessage}}" userInput="The database was backed up." stepKey="seeAssertDatabaseBackedUpMessage"/> <see selector="{{AdminStoresGridSection.successMessage}}" userInput="You deleted the store." stepKey="seeAssertSuccessDeleteStoreGroupMessage"/> </actionGroup> diff --git a/app/code/Magento/Store/Test/Mftf/Helper/StoreHelpers.php b/app/code/Magento/Store/Test/Mftf/Helper/StoreHelpers.php new file mode 100644 index 0000000000000..79c77c4ddbc75 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Helper/StoreHelpers.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Test\Mftf\Helper; + +use Facebook\WebDriver\Remote\RemoteWebDriver as FacebookWebDriver; +use Facebook\WebDriver\WebDriverBy; +use Magento\FunctionalTestingFramework\Helper\Helper; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; + +/** + * Class for MFTF helpers for Store module. + */ +class StoreHelpers extends Helper +{ + /** + * Delete all specified Websites one by one from the admin Stores page. + * + * @param string $websitesToDelete + * @param string $deleteButton + * @param string $createDbBackupButton + * @param string $successMessage + * @param string $successMessageContainer + * + * @return void + */ + public function deleteAllSpecifiedWebsites( + string $websitesToDelete, + string $deleteButton, + string $createDbBackupButton, + string $successMessage, + string $successMessageContainer + ): void { + try { + $magentoWebDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + /** @var FacebookWebDriver $webDriver */ + $webDriver = $magentoWebDriver->webDriver; + + $magentoWebDriver->waitForPageLoad(30); + $websites = $webDriver->findElements(WebDriverBy::xpath($websitesToDelete)); + while (!empty($websites)) { + $websites[0]->click(); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($deleteButton, 10); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($createDbBackupButton, 10); + $magentoWebDriver->selectOption($createDbBackupButton, 'No'); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(60); + $magentoWebDriver->waitForText($successMessage, 10, $successMessageContainer); + $websites = $webDriver->findElements(WebDriverBy::xpath($websitesToDelete)); + } + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } + + /** + * Delete all specified Stores one by one from the admin Stores page. + * + * @param string $storesToDelete + * @param string $deleteButton + * @param string $createDbBackupButton + * @param string $successMessage + * @param string $successMessageContainer + * + * @return void + */ + public function deleteAllSpecifiedStores( + string $storesToDelete, + string $deleteButton, + string $createDbBackupButton, + string $successMessage, + string $successMessageContainer + ): void { + try { + $magentoWebDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + /** @var FacebookWebDriver $webDriver */ + $webDriver = $magentoWebDriver->webDriver; + + $magentoWebDriver->waitForPageLoad(30); + $stores = $webDriver->findElements(WebDriverBy::xpath($storesToDelete)); + while (!empty($stores)) { + $stores[0]->click(); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($deleteButton, 10); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($createDbBackupButton, 10); + $magentoWebDriver->selectOption($createDbBackupButton, 'No'); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(60); + $magentoWebDriver->waitForText($successMessage, 10, $successMessageContainer); + $stores = $webDriver->findElements(WebDriverBy::xpath($storesToDelete)); + } + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } + + /** + * Delete all specified Store Views one by one from the admin Stores page. + * + * @param string $storeViewsToDelete + * @param string $deleteButton + * @param string $createDbBackupButton + * @param string $successMessage + * @param string $successMessageContainer + * + * @return void + */ + public function deleteAllSpecifiedStoreViews( + string $storeViewsToDelete, + string $deleteButton, + string $createDbBackupButton, + string $successMessage, + string $successMessageContainer + ): void { + try { + $magentoWebDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + /** @var FacebookWebDriver $webDriver */ + $webDriver = $magentoWebDriver->webDriver; + + $magentoWebDriver->waitForPageLoad(30); + $storeViews = $webDriver->findElements(WebDriverBy::xpath($storeViewsToDelete)); + while (!empty($storeViews)) { + $storeViews[0]->click(); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($deleteButton, 10); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($createDbBackupButton, 10); + $magentoWebDriver->selectOption($createDbBackupButton, 'No'); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(60); + $magentoWebDriver->waitForText($successMessage, 10, $successMessageContainer); + $storeViews = $webDriver->findElements(WebDriverBy::xpath($storeViewsToDelete)); + } + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } +} diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml index 35f4507a80f81..6af8d4d9f796d 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml @@ -14,5 +14,11 @@ <element name="backButton" type="button" selector="#back" timeout="30"/> <element name="deleteButton" type="button" selector="#delete" timeout="30"/> <element name="errorMessage" type="text" selector="//div[@class='message message-error error']/div"/> + <element name="allNonDefaultWebsites" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//a[not(text()='Main Website')]/ancestor::td[@data-column='website_title']//a"/> + <element name="allWebsites" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//td[@data-column='website_title']"/> + <element name="allNonDefaultStores" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//a[not(text()='Main Website Store')]/ancestor::td[@data-column='group_title']//a"/> + <element name="allStores" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//td[@data-column='group_title']"/> + <element name="allNonDefaultStoreViews" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//a[not(text()='Default Store View')]/ancestor::td[@data-column='store_title']//a"/> + <element name="allStoreViews" type="block" selector="//table[@id='storeGrid_table']//tbody//tr//td[@data-column='store_title']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json index 4168a362e2a57..6ce8cdae8a96d 100644 --- a/app/code/Magento/Store/composer.json +++ b/app/code/Magento/Store/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*", diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index 1e21a85c7b091..3f520f04a203f 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/Swagger/composer.json b/app/code/Magento/Swagger/composer.json index ba5e4f64b967d..87c2d484a8032 100644 --- a/app/code/Magento/Swagger/composer.json +++ b/app/code/Magento/Swagger/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/SwaggerWebapi/composer.json b/app/code/Magento/SwaggerWebapi/composer.json index c27d025036c97..6b7ae11ffdab5 100644 --- a/app/code/Magento/SwaggerWebapi/composer.json +++ b/app/code/Magento/SwaggerWebapi/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-swagger": "*" }, diff --git a/app/code/Magento/SwaggerWebapiAsync/composer.json b/app/code/Magento/SwaggerWebapiAsync/composer.json index 6347155179d3d..fae8f0b196ae0 100644 --- a/app/code/Magento/SwaggerWebapiAsync/composer.json +++ b/app/code/Magento/SwaggerWebapiAsync/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-swagger": "*" }, diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml index 0d49acc665650..9764d96aacd27 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml @@ -33,6 +33,13 @@ <click selector="{{AdminManageSwatchSection.nthDelete('3')}}" stepKey="deleteSwatch3"/> <waitForPageLoad stepKey="waitToClickSave"/> <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit"/> + <waitForPageLoad stepKey="waitForSave"/> + + <!-- Change back to dropdown --> + <selectOption selector="{{AdminNewAttributePanel.inputType}}" userInput="select" stepKey="selectDropdown"/> + <waitForPageLoad stepKey="waitToClickSave2"/> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit2"/> + <waitForPageLoad stepKey="waitForSave2"/> <!-- Logout --> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index a1c0b3adeaae4..be0acdd8e8ef5 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/text.js b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js index 48a14c14bdf3a..92b000f253dfb 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/text.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js @@ -214,7 +214,7 @@ define([ }); } - jQuery(document).ready(function () { + jQuery(function () { if (jQuery('#frontend_input').val() !== 'swatch_text') { jQuery('.swatch-text-field-0').removeClass('required-option'); } diff --git a/app/code/Magento/SwatchesGraphQl/composer.json b/app/code/Magento/SwatchesGraphQl/composer.json index 40f87294affa9..630c083371dc9 100644 --- a/app/code/Magento/SwatchesGraphQl/composer.json +++ b/app/code/Magento/SwatchesGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-swatches": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/SwatchesLayeredNavigation/composer.json b/app/code/Magento/SwatchesLayeredNavigation/composer.json index e2c6eec8570b3..ab646b40615bc 100644 --- a/app/code/Magento/SwatchesLayeredNavigation/composer.json +++ b/app/code/Magento/SwatchesLayeredNavigation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/magento-composer-installer": "*" }, diff --git a/app/code/Magento/Tax/Controller/Adminhtml/Rule/AjaxLoadRates.php b/app/code/Magento/Tax/Controller/Adminhtml/Rule/AjaxLoadRates.php index 060a4350bb624..b93b12be40270 100644 --- a/app/code/Magento/Tax/Controller/Adminhtml/Rule/AjaxLoadRates.php +++ b/app/code/Magento/Tax/Controller/Adminhtml/Rule/AjaxLoadRates.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\ResultFactory; use Magento\Tax\Model\Rate\Provider as RatesProvider; @@ -17,7 +18,7 @@ * Class AjaxLoadRates is intended to load existing * Tax rates as options for a select element. */ -class AjaxLoadRates extends Action +class AjaxLoadRates extends Action implements HttpGetActionInterface { /** * @var RatesProvider @@ -53,7 +54,7 @@ public function __construct( public function execute() { $ratesPage = (int) $this->getRequest()->getParam('p'); - $ratesFilter = trim($this->getRequest()->getParam('s')); + $ratesFilter = trim($this->getRequest()->getParam('s', '')); try { if (!empty($ratesFilter)) { diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteMultipleTaxRatesActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteMultipleTaxRatesActionGroup.xml index f472b5c1b9407..e9ebdc3009f30 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteMultipleTaxRatesActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteMultipleTaxRatesActionGroup.xml @@ -9,8 +9,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminDeleteMultipleTaxRatesActionGroup"> <annotations> - <description>Navigates to the 'Tax Zones and Rates' page and deletes all specified rows one by one. Defaults - to delete all rows except the defaults of 'US-CA-*-Rate 1' and 'US-NY-*-Rate 1'.</description> + <description>Deletes all specified rows one by one on the 'Tax Zones and Rates' page. Defaults to delete all + rows except the defaults of 'US-CA-*-Rate 1' and 'US-NY-*-Rate 1'.</description> </annotations> <arguments> <argument name="rowsToDelete" defaultValue="{{AdminTaxRateGridSection.allNonDefaultTaxRates}}" type="string"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml index 5fc9b7c16294b..8df4f85cc5357 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml @@ -76,7 +76,7 @@ <waitForPageLoad stepKey="waitForSimpleProductPage"/> <click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for CA --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml index 73fd5c144d45a..107a5a9d828cb 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml @@ -76,7 +76,7 @@ <waitForPageLoad stepKey="waitForVirtualProductPage"/> <click stepKey="addVirtualProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml index 20c7833c96015..23350be350c16 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml @@ -90,7 +90,7 @@ <waitForPageLoad stepKey="waitForSimpleProductPage"/> <click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml index 4ce4d3920b16b..68282d37aa470 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml @@ -89,7 +89,7 @@ <waitForPageLoad stepKey="waitForVirtualProductPage"/> <click stepKey="addVirtualProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml index 8c28fb36eb34e..a21dd1a7fa05b 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml @@ -76,7 +76,7 @@ <waitForPageLoad stepKey="waitForSimpleProductPage"/> <click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Fill in address for CA --> <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml index ac012f36f74f9..4bc664dbe3fdd 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml @@ -76,7 +76,7 @@ <waitForPageLoad stepKey="waitForVirtualProductPage"/> <click stepKey="addVirtualProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for CA --> <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml index de7c346fa2b10..294e6e909bed0 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml @@ -91,7 +91,7 @@ <waitForPageLoad stepKey="waitForSimpleProductPage"/> <click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml index a0a881f32a902..d8795e80ea500 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml @@ -43,6 +43,7 @@ <magentoCLI command="cron:run --group=index" stepKey="runCronIndexer"/> </before> <after> + <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct"/> <!-- Go to the tax rule page and delete the row we created--> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRulesPage"/> <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> @@ -66,9 +67,13 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{Simple_US_Customer_NY.email}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetGrid"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> - <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct1"/> + <comment userInput="BIC workaround" stepKey="deleteVirtualProduct1"/> </after> <!-- Fill out form for a new user with address --> @@ -91,7 +96,7 @@ <waitForPageLoad stepKey="waitForVirtualProductPage"/> <click stepKey="addVirtualProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForProductAdded"/> - <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> + <see stepKey="seeSuccess" selector="{{StorefrontMessagesSection.success}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Tax/composer.json b/app/code/Magento/Tax/composer.json index 03fedd22a5aa7..aa421c77212bf 100644 --- a/app/code/Magento/Tax/composer.json +++ b/app/code/Magento/Tax/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/TaxGraphQl/composer.json b/app/code/Magento/TaxGraphQl/composer.json index 788e3ee78ee7d..cdd09ee8291db 100644 --- a/app/code/Magento/TaxGraphQl/composer.json +++ b/app/code/Magento/TaxGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/TaxImportExport/composer.json b/app/code/Magento/TaxImportExport/composer.json index c5b7b35cf23ae..e1af1f95da612 100644 --- a/app/code/Magento/TaxImportExport/composer.json +++ b/app/code/Magento/TaxImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-directory": "*", diff --git a/app/code/Magento/Theme/Controller/Result/MessagePlugin.php b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php index 20d0165e84740..056f1b21a9be8 100644 --- a/app/code/Magento/Theme/Controller/Result/MessagePlugin.php +++ b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php @@ -7,7 +7,7 @@ use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\ResultInterface; -use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException; use Magento\Framework\Translate\Inline\ParserInterface; use Magento\Framework\Translate\InlineInterface; use Magento\Framework\Session\Config\ConfigInterface; @@ -22,7 +22,7 @@ class MessagePlugin /** * Cookies name for messages */ - const MESSAGES_COOKIES_NAME = 'mage-messages'; + public const MESSAGES_COOKIES_NAME = 'mage-messages'; /** * @var \Magento\Framework\Stdlib\CookieManagerInterface @@ -101,11 +101,44 @@ public function afterRenderResult( ResultInterface $result ) { if (!($subject instanceof Json)) { - $this->setCookie($this->getMessages()); + $newMessages = []; + foreach ($this->messageManager->getMessages(true)->getItems() as $message) { + $newMessages[] = [ + 'type' => $message->getType(), + 'text' => $this->interpretationStrategy->interpret($message), + ]; + } + if (!empty($newMessages)) { + $this->setMessages($this->getCookiesMessages(), $newMessages); + } } return $result; } + /** + * Add new messages to already existing ones. + * + * In case if there are too many messages clear old messages. + * + * @param array $oldMessages + * @param array $newMessages + * @throws CookieSizeLimitReachedException + */ + private function setMessages(array $oldMessages, array $newMessages): void + { + $messages = array_merge($oldMessages, $newMessages); + try { + $this->setCookie($messages); + } catch (CookieSizeLimitReachedException $e) { + if (empty($oldMessages)) { + throw $e; + } + + array_shift($oldMessages); + $this->setMessages($oldMessages, $newMessages); + } + } + /** * Set 'mage-messages' cookie with 'messages' array * @@ -166,24 +199,6 @@ private function convertMessageText(string $text): string return $text; } - /** - * Return messages array and clean message manager messages - * - * @return array - */ - protected function getMessages() - { - $messages = $this->getCookiesMessages(); - /** @var MessageInterface $message */ - foreach ($this->messageManager->getMessages(true)->getItems() as $message) { - $messages[] = [ - 'type' => $message->getType(), - 'text' => $this->interpretationStrategy->interpret($message), - ]; - } - return $messages; - } - /** * Return messages stored in cookies * diff --git a/app/code/Magento/Theme/CustomerData/Messages.php b/app/code/Magento/Theme/CustomerData/Messages.php index adb3f7df27395..15575b1c1c7e0 100644 --- a/app/code/Magento/Theme/CustomerData/Messages.php +++ b/app/code/Magento/Theme/CustomerData/Messages.php @@ -7,6 +7,7 @@ namespace Magento\Theme\CustomerData; use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Message\ManagerInterface as MessageManager; use Magento\Framework\Message\MessageInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; @@ -28,18 +29,27 @@ class Messages implements SectionSourceInterface */ private $interpretationStrategy; + /** + * @var MessagesProviderInterface + */ + private $messageProvider; + /** * Constructor * * @param MessageManager $messageManager * @param InterpretationStrategyInterface $interpretationStrategy + * @param MessagesProviderInterface|null $messageProvider */ public function __construct( MessageManager $messageManager, - InterpretationStrategyInterface $interpretationStrategy + InterpretationStrategyInterface $interpretationStrategy, + ?MessagesProviderInterface $messageProvider = null ) { $this->messageManager = $messageManager; $this->interpretationStrategy = $interpretationStrategy; + $this->messageProvider = $messageProvider + ?? ObjectManager::getInstance()->get(MessagesProviderInterface::class); } /** @@ -47,19 +57,20 @@ public function __construct( */ public function getSectionData() { - $messages = $this->messageManager->getMessages(true); + $messages = $this->messageProvider->getMessages(); + $messageResponse = array_reduce( + $messages->getItems(), + function (array $result, MessageInterface $message) { + $result[] = [ + 'type' => $message->getType(), + 'text' => $this->interpretationStrategy->interpret($message) + ]; + return $result; + }, + [] + ); return [ - 'messages' => array_reduce( - $messages->getItems(), - function (array $result, MessageInterface $message) { - $result[] = [ - 'type' => $message->getType(), - 'text' => $this->interpretationStrategy->interpret($message) - ]; - return $result; - }, - [] - ), + 'messages' => $messageResponse ]; } } diff --git a/app/code/Magento/Theme/CustomerData/MessagesProvider.php b/app/code/Magento/Theme/CustomerData/MessagesProvider.php new file mode 100644 index 0000000000000..360f348cb2a45 --- /dev/null +++ b/app/code/Magento/Theme/CustomerData/MessagesProvider.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Theme\CustomerData; + +use Magento\Framework\Message\Collection; +use Magento\Framework\Message\ManagerInterface as MessageManager; + +class MessagesProvider implements MessagesProviderInterface +{ + /** + * Manager messages + * + * @var MessageManager + */ + private $messageManager; + + /** + * Constructor + * + * @param MessageManager $messageManager + */ + public function __construct( + MessageManager $messageManager + ) { + $this->messageManager = $messageManager; + } + + /** + * Return collection object of messages from session + * + * @return Collection + */ + public function getMessages() : Collection + { + return $this->messageManager->getMessages(true); + } +} diff --git a/app/code/Magento/Theme/CustomerData/MessagesProviderInterface.php b/app/code/Magento/Theme/CustomerData/MessagesProviderInterface.php new file mode 100644 index 0000000000000..cfa281e8aab42 --- /dev/null +++ b/app/code/Magento/Theme/CustomerData/MessagesProviderInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Theme\CustomerData; + +use Magento\Framework\Message\Collection; + +interface MessagesProviderInterface +{ + /** + * Get the messages stored in session before session clear + * + * @return Collection + */ + public function getMessages(): Collection; +} diff --git a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php index 5c38d99dd6a22..14df7270d6421 100644 --- a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php +++ b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php @@ -8,7 +8,9 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem\DriverInterface; +use Magento\MediaStorage\Model\File\Uploader; /** * Theme wysiwyg storage model @@ -22,34 +24,34 @@ class Storage * * Represents the font type */ - const TYPE_FONT = 'font'; + public const TYPE_FONT = 'font'; /** * Type image * * Represents the image type */ - const TYPE_IMAGE = 'image'; + public const TYPE_IMAGE = 'image'; /** * \Directory for image thumbnail */ - const THUMBNAIL_DIRECTORY = '.thumbnail'; + public const THUMBNAIL_DIRECTORY = '.thumbnail'; /** * Image thumbnail width */ - const THUMBNAIL_WIDTH = 100; + public const THUMBNAIL_WIDTH = 100; /** * Image thumbnail height */ - const THUMBNAIL_HEIGHT = 100; + public const THUMBNAIL_HEIGHT = 100; /** * \Directory name regular expression */ - const DIRECTORY_NAME_REGEXP = '/^[a-z0-9\-\_]+$/si'; + public const DIRECTORY_NAME_REGEXP = '/^[a-z0-9\-\_]+$/si'; /** * Storage helper @@ -134,24 +136,24 @@ public function __construct( * * @param string $targetPath * @return array - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function uploadFile($targetPath) { - /** @var $uploader \Magento\MediaStorage\Model\File\Uploader */ + /** @var $uploader Uploader */ $uploader = $this->_objectManager->create( - \Magento\MediaStorage\Model\File\Uploader::class, + Uploader::class, ['fileId' => 'file'] ); $uploader->setAllowedExtensions($this->_helper->getAllowedExtensionsByType()); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); $result = $uploader->save($targetPath); - unset($result['path']); if (!$result) { - throw new \Magento\Framework\Exception\LocalizedException(__('We can\'t upload the file right now.')); + throw new LocalizedException(__('We can\'t upload the file right now.')); } + unset($result['path']); $this->_createThumbnail($targetPath . '/' . $uploader->getUploadedFileName()); @@ -200,12 +202,12 @@ public function _createThumbnail($source) * @param string $name * @param string $path * @return array - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function createFolder($name, $path) { if (!preg_match(self::DIRECTORY_NAME_REGEXP, $name)) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Use only standard alphanumeric, dashes and underscores.') ); } @@ -216,7 +218,7 @@ public function createFolder($name, $path) $newPath = $path . '/' . $name; if ($this->mediaWriteDirectory->isExist($newPath)) { - throw new \Magento\Framework\Exception\LocalizedException(__('We found a directory with the same name.')); + throw new LocalizedException(__('We found a directory with the same name.')); } $this->mediaWriteDirectory->create($newPath); @@ -259,12 +261,12 @@ public function deleteFile($file) * * @param string $currentPath * @return array - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function getDirsCollection($currentPath) { if (!$this->mediaWriteDirectory->isExist($currentPath)) { - throw new \Magento\Framework\Exception\LocalizedException(__('We cannot find a directory with this name.')); + throw new LocalizedException(__('We cannot find a directory with this name.')); } $paths = $this->mediaWriteDirectory->search('.*', $currentPath); $directories = []; @@ -296,7 +298,7 @@ public function getFilesCollection() if (self::TYPE_IMAGE == $storageType) { $requestParams['file'] = $fileName; $file['thumbnailParams'] = $requestParams; - //phpcs:ignore Generic.PHP.NoSilencedErrors + // phpcs:ignore Generic.PHP.NoSilencedErrors, Magento2.Functions.DiscouragedFunction $size = @getimagesize($path); if (is_array($size)) { $file['width'] = $size[0]; @@ -335,7 +337,7 @@ public function getTreeArray() * * @param string $path * @return bool - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function deleteDirectory($path) { @@ -347,7 +349,7 @@ public function deleteDirectory($path) ); if ($rootCmp == $pathCmp || $rootCmp === $absolutePath) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('We can\'t delete root directory %1 right now.', $path) ); } diff --git a/app/code/Magento/Theme/Test/Mftf/Test/AdminWatermarkUploadTest.xml b/app/code/Magento/Theme/Test/Mftf/Test/AdminWatermarkUploadTest.xml index 6895e167e206a..69673fa5e6daf 100644 --- a/app/code/Magento/Theme/Test/Mftf/Test/AdminWatermarkUploadTest.xml +++ b/app/code/Magento/Theme/Test/Mftf/Test/AdminWatermarkUploadTest.xml @@ -26,6 +26,7 @@ </after> <amOnPage url="{{DesignConfigPage.url}}" stepKey="navigateToDesignConfigPage" /> <waitForPageLoad stepKey="waitForPageload1"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilters"/> <click selector="{{AdminDesignConfigSection.scopeRow('3')}}" stepKey="editStoreView"/> <waitForPageLoad stepKey="waitForPageload2"/> <scrollTo selector="{{AdminDesignConfigSection.watermarkSectionHeader}}" stepKey="scrollToWatermarkSection"/> diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php index 5f365e6a82eae..da679ba524867 100644 --- a/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php +++ b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php @@ -12,6 +12,7 @@ use Magento\Framework\Message\Collection; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\Cookie\PublicCookieMetadata; use Magento\Framework\Stdlib\CookieManagerInterface; @@ -28,21 +29,21 @@ class MessagePluginTest extends TestCase { /** @var MessagePlugin */ - protected $model; + private $model; /** @var CookieManagerInterface|MockObject */ - protected $cookieManagerMock; + private $cookieManagerMock; /** @var CookieMetadataFactory|MockObject */ - protected $cookieMetadataFactoryMock; + private $cookieMetadataFactoryMock; /** @var ManagerInterface|MockObject */ - protected $managerMock; + private $managerMock; /** @var InterpretationStrategyInterface|MockObject */ - protected $interpretationStrategyMock; + private $interpretationStrategyMock; - /** @var \Magento\Framework\Serialize\Serializer\Json|MockObject */ + /** @var JsonSerializer|MockObject */ private $serializerMock; /** @var InlineInterface|MockObject */ @@ -51,25 +52,17 @@ class MessagePluginTest extends TestCase /** * @var ConfigInterface|MockObject */ - protected $sessionConfigMock; + private $sessionConfigMock; protected function setUp(): void { - $this->cookieManagerMock = $this->getMockBuilder(CookieManagerInterface::class) - ->getMockForAbstractClass(); - $this->cookieMetadataFactoryMock = $this->getMockBuilder(CookieMetadataFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->managerMock = $this->getMockBuilder(ManagerInterface::class) - ->getMockForAbstractClass(); - $this->interpretationStrategyMock = $this->getMockBuilder(InterpretationStrategyInterface::class) - ->getMockForAbstractClass(); - $this->serializerMock = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) - ->getMock(); - $this->inlineTranslateMock = $this->getMockBuilder(InlineInterface::class) - ->getMockForAbstractClass(); - $this->sessionConfigMock = $this->getMockBuilder(ConfigInterface::class) - ->getMockForAbstractClass(); + $this->cookieManagerMock = $this->createMock(CookieManagerInterface::class); + $this->cookieMetadataFactoryMock = $this->createMock(CookieMetadataFactory::class); + $this->managerMock = $this->createMock(ManagerInterface::class); + $this->interpretationStrategyMock = $this->createMock(InterpretationStrategyInterface::class); + $this->serializerMock = $this->createMock(JsonSerializer::class); + $this->inlineTranslateMock = $this->createMock(InlineInterface::class); + $this->sessionConfigMock = $this->createMock(ConfigInterface::class); $this->model = new MessagePlugin( $this->cookieManagerMock, @@ -85,9 +78,7 @@ protected function setUp(): void public function testAfterRenderResultJson() { /** @var Json|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Json::class) - ->disableOriginalConstructor() - ->getMock(); + $resultMock = $this->createMock(Json::class); $this->cookieManagerMock->expects($this->never()) ->method('setPublicCookie'); @@ -114,19 +105,12 @@ public function testAfterRenderResult() $messages = array_merge($existingMessages, $messages); /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); - $this->cookieManagerMock->expects($this->once()) ->method('setPublicCookie') ->with( @@ -157,25 +141,20 @@ function ($data) { ); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); + $messageMock = $this->createMock(MessageInterface::class); $messageMock->expects($this->once()) ->method('getType') ->willReturn($messageType); - $this->interpretationStrategyMock->expects($this->once()) ->method('interpret') ->with($messageMock) ->willReturn($messageText); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) @@ -187,43 +166,27 @@ function ($data) { public function testAfterRenderResultWithNoMessages() { /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->cookieManagerMock->expects($this->once()) - ->method('getCookie') - ->with( - MessagePlugin::MESSAGES_COOKIES_NAME - ) - ->willReturn(json_encode([])); + $resultMock = $this->createMock(Redirect::class); - $this->serializerMock->expects($this->once()) - ->method('unserialize') - ->willReturnCallback( - function ($data) { - return json_decode($data, true); - } - ); + $this->cookieManagerMock->expects($this->never()) + ->method('getCookie'); + $this->serializerMock->expects($this->never()) + ->method('unserialize'); $this->serializerMock->expects($this->never()) ->method('serialize'); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) ->willReturn($collectionMock); $this->cookieMetadataFactoryMock->expects($this->never()) - ->method('createPublicCookieMetadata') - ->willReturn(null); + ->method('createPublicCookieMetadata'); $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); } @@ -240,19 +203,12 @@ public function testAfterRenderResultWithoutExisting() ]; /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); - $this->cookieManagerMock->expects($this->once()) ->method('setPublicCookie') ->with( @@ -283,25 +239,20 @@ function ($data) { ); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); + $messageMock = $this->createMock(MessageInterface::class); $messageMock->expects($this->once()) ->method('getType') ->willReturn($messageType); - $this->interpretationStrategyMock->expects($this->once()) ->method('interpret') ->with($messageMock) ->willReturn($messageText); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) @@ -322,19 +273,12 @@ public function testAfterRenderResultWithWrongJson() ]; /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); - $this->cookieManagerMock->expects($this->once()) ->method('setPublicCookie') ->with( @@ -351,7 +295,6 @@ public function testAfterRenderResultWithWrongJson() $this->serializerMock->expects($this->never()) ->method('unserialize'); - $this->serializerMock->expects($this->once()) ->method('serialize') ->willReturnCallback( @@ -361,25 +304,20 @@ function ($data) { ); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); + $messageMock = $this->createMock(MessageInterface::class); $messageMock->expects($this->once()) ->method('getType') ->willReturn($messageType); - $this->interpretationStrategyMock->expects($this->once()) ->method('interpret') ->with($messageMock) ->willReturn($messageText); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) @@ -400,15 +338,9 @@ public function testAfterRenderResultWithWrongArray() ]; /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); @@ -443,25 +375,20 @@ function ($data) { ); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); + $messageMock = $this->createMock(MessageInterface::class); $messageMock->expects($this->once()) ->method('getType') ->willReturn($messageType); - $this->interpretationStrategyMock->expects($this->once()) ->method('interpret') ->with($messageMock) ->willReturn($messageText); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) @@ -485,19 +412,12 @@ public function testAfterRenderResultWithAllowedInlineTranslate(): void ]; /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); - $this->cookieManagerMock->expects($this->once()) ->method('setPublicCookie') ->with( @@ -528,12 +448,10 @@ function ($data) { ); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); + $messageMock = $this->createMock(MessageInterface::class); $messageMock->expects($this->once()) ->method('getType') ->willReturn($messageType); - $this->interpretationStrategyMock->expects($this->once()) ->method('interpret') ->with($messageMock) @@ -544,13 +462,10 @@ function ($data) { ->willReturn(true); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); - $this->managerMock->expects($this->once()) ->method('getMessages') ->with(true, null) @@ -562,27 +477,17 @@ function ($data) { public function testSetCookieWithCookiePath() { /** @var Redirect|MockObject $resultMock */ - $resultMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - + $resultMock = $this->createMock(Redirect::class); /** @var PublicCookieMetadata|MockObject $cookieMetadataMock */ - $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - + $cookieMetadataMock = $this->createMock(PublicCookieMetadata::class); $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createPublicCookieMetadata') ->willReturn($cookieMetadataMock); /** @var MessageInterface|MockObject $messageMock */ - $messageMock = $this->getMockBuilder(MessageInterface::class) - ->getMock(); - + $messageMock = $this->createMock(MessageInterface::class); /** @var Collection|MockObject $collectionMock */ - $collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); + $collectionMock = $this->createMock(Collection::class); $collectionMock->expects($this->once()) ->method('getItems') ->willReturn([$messageMock]); diff --git a/app/code/Magento/Theme/Test/Unit/CustomerData/MessagesTest.php b/app/code/Magento/Theme/Test/Unit/CustomerData/MessagesTest.php index cb07730c9d8d1..44ccb2b8b3dfb 100644 --- a/app/code/Magento/Theme/Test/Unit/CustomerData/MessagesTest.php +++ b/app/code/Magento/Theme/Test/Unit/CustomerData/MessagesTest.php @@ -12,6 +12,7 @@ use Magento\Framework\Message\MessageInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Theme\CustomerData\Messages; +use Magento\Theme\CustomerData\MessagesProviderInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -22,6 +23,11 @@ class MessagesTest extends TestCase */ protected $messageManager; + /** + * @var MessagesProviderInterface|MockObject + */ + private $messageProvider; + /** * @var InterpretationStrategyInterface|MockObject */ @@ -36,10 +42,16 @@ protected function setUp(): void { $this->messageManager = $this->getMockBuilder(ManagerInterface::class) ->getMock(); + $this->messageProvider = $this->getMockBuilder(MessagesProviderInterface::class) + ->getMock(); $this->messageInterpretationStrategy = $this->createMock( InterpretationStrategyInterface::class ); - $this->object = new Messages($this->messageManager, $this->messageInterpretationStrategy); + $this->object = new Messages( + $this->messageManager, + $this->messageInterpretationStrategy, + $this->messageProvider + ); } public function testGetSectionData() @@ -59,9 +71,8 @@ public function testGetSectionData() ->method('interpret') ->with($msg) ->willReturn($msgText); - $this->messageManager->expects($this->once()) + $this->messageProvider->expects($this->once()) ->method('getMessages') - ->with(true, null) ->willReturn($msgCollection); $msgCollection->expects($this->once()) ->method('getItems') diff --git a/app/code/Magento/Theme/composer.json b/app/code/Magento/Theme/composer.json index a6effb072f5f9..75b2f434e0fdc 100644 --- a/app/code/Magento/Theme/composer.json +++ b/app/code/Magento/Theme/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index d6fe3f8fef355..6ea495e2702ae 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -19,6 +19,7 @@ <preference for="Magento\Framework\View\Model\PageLayout\Config\BuilderInterface" type="Magento\Theme\Model\PageLayout\Config\Builder"/> <preference for="Magento\Theme\Model\Design\Config\MetadataProviderInterface" type="Magento\Theme\Model\Design\Config\MetadataProvider"/> <preference for="Magento\Theme\Model\Theme\StoreThemesResolverInterface" type="Magento\Theme\Model\Theme\StoreThemesResolver"/> + <preference for="Magento\Theme\CustomerData\MessagesProviderInterface" type="Magento\Theme\CustomerData\MessagesProvider"/> <type name="Magento\Theme\Model\Config"> <arguments> <argument name="configCache" xsi:type="object">Magento\Framework\App\Cache\Type\Config</argument> diff --git a/app/code/Magento/Theme/view/adminhtml/requirejs-config.js b/app/code/Magento/Theme/view/adminhtml/requirejs-config.js index 948bd2a57c30b..431e046049993 100644 --- a/app/code/Magento/Theme/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Theme/view/adminhtml/requirejs-config.js @@ -88,6 +88,13 @@ var config = { 'mage/backend/bootstrap', 'mage/adminhtml/globals' ], + config: { + mixins: { + 'jquery/jquery-ui': { + 'jquery/patches/jquery-ui-sortable': true + } + } + }, 'paths': { 'jquery/ui': 'jquery/jquery-ui' } diff --git a/app/code/Magento/Theme/view/frontend/requirejs-config.js b/app/code/Magento/Theme/view/frontend/requirejs-config.js index 0a064cd0cd3da..fb649f75d90bc 100644 --- a/app/code/Magento/Theme/view/frontend/requirejs-config.js +++ b/app/code/Magento/Theme/view/frontend/requirejs-config.js @@ -40,6 +40,9 @@ var config = { mixins: { 'Magento_Theme/js/view/breadcrumbs': { 'Magento_Theme/js/view/add-home-breadcrumb': true + }, + 'jquery/ui-modules/widgets/sortable': { + 'jquery/patches/jquery-ui-sortable': true } } } diff --git a/app/code/Magento/ThemeGraphQl/composer.json b/app/code/Magento/ThemeGraphQl/composer.json index 8be59a4355831..c8550cd52d6a5 100644 --- a/app/code/Magento/ThemeGraphQl/composer.json +++ b/app/code/Magento/ThemeGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index c21790c40e974..710b0a7c90265 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-developer": "*", diff --git a/app/code/Magento/Ui/DataProvider/AbstractDataProvider.php b/app/code/Magento/Ui/DataProvider/AbstractDataProvider.php index 6e4e488619e86..c8a483d26d80c 100644 --- a/app/code/Magento/Ui/DataProvider/AbstractDataProvider.php +++ b/app/code/Magento/Ui/DataProvider/AbstractDataProvider.php @@ -8,7 +8,7 @@ use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; - // phpcs:disable Magento2.Classes.AbstractApi +// phpcs:disable Magento2.Classes.AbstractApi /** * @inheritdoc * @@ -165,6 +165,7 @@ public function getFieldMetaInfo($fieldSetName, $fieldName) */ public function addFilter(\Magento\Framework\Api\Filter $filter) { + // @phpstan-ignore-next-line as adding return statement cause of backward compatibility issue $this->getCollection()->addFieldToFilter( $filter->getField(), [$filter->getConditionType() => $filter->getValue()] @@ -267,6 +268,7 @@ public function getData() * * @return int */ + #[\ReturnTypeWillChange] public function count() { return $this->getCollection()->count(); diff --git a/app/code/Magento/Ui/Model/Export/SearchResultIterator.php b/app/code/Magento/Ui/Model/Export/SearchResultIterator.php index e84a628e6699e..da0004f405ca5 100644 --- a/app/code/Magento/Ui/Model/Export/SearchResultIterator.php +++ b/app/code/Magento/Ui/Model/Export/SearchResultIterator.php @@ -22,40 +22,45 @@ public function __construct( } /** - * @return array|mixed + * @inheritdoc */ + #[\ReturnTypeWillChange] public function current() { return current($this->items); } /** - * @return int|mixed + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { return key($this->items); } /** - * @return void + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->items); } /** - * @return void + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->items); } /** - * @return bool + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return $this->key() !== null; diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridDeselectAllActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridDeselectAllActionGroup.xml new file mode 100644 index 0000000000000..3afa78002e2a4 --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridDeselectAllActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminGridDeselectAllActionGroup"> + <annotations> + <description>Click on "Deselect All" option on the grid</description> + </annotations> + + <waitForElementVisible selector="{{AdminGridSelectRows.multicheckDropdown}}" stepKey="waitForElement"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminGridSelectRows.multicheckDropdown}}" stepKey="openMulticheckDropdown"/> + <click selector="{{AdminGridSelectRows.multicheckOption('Deselect All')}}" stepKey="clickDeselectAllCustomers"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSelectAllActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSelectAllActionGroup.xml index bbfb7e46d89ec..cf379e94c36c4 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSelectAllActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridSelectAllActionGroup.xml @@ -10,10 +10,11 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminGridSelectAllActionGroup"> <annotations> - <description>Click on select all option on the grid</description> + <description>Click on "Select All" option on the grid</description> </annotations> <waitForElementVisible selector="{{AdminGridSelectRows.multicheckDropdown}}" stepKey="waitForElement"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> <click selector="{{AdminGridSelectRows.multicheckDropdown}}" stepKey="openMulticheckDropdown"/> <click selector="{{AdminGridSelectRows.multicheckOption('Select All')}}" stepKey="clickSelectAllCustomers"/> </actionGroup> diff --git a/app/code/Magento/Ui/composer.json b/app/code/Magento/Ui/composer.json index 069231f285d9f..802d94ddd7349 100644 --- a/app/code/Magento/Ui/composer.json +++ b/app/code/Magento/Ui/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 5f7571aa7246d..b9a147e2609de 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -1101,6 +1101,22 @@ define([ return moment.utc(value, params.dateFormat).isSameOrBefore(moment.utc()); }, $.mage.__('The Date of Birth should not be greater than today.') + ], + 'validate-no-utf8mb4-characters': [ + function (value) { + var validator = this, + message = $.mage.__('Please remove invalid characters: {0}.'), + matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g), + result = matches === null; + + if (!result) { + validator.charErrorMessage = message.replace('{0}', matches.join()); + } + + return result; + }, function () { + return this.charErrorMessage; + } ] }, function (data) { return { diff --git a/app/code/Magento/Ups/composer.json b/app/code/Magento/Ups/composer.json index 4a9c4e32ab734..21fe972938a3c 100644 --- a/app/code/Magento/Ups/composer.json +++ b/app/code/Magento/Ups/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml index 2dd7df9cbb548..006c01c23da0b 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml @@ -21,8 +21,10 @@ <comment userInput="Set the configuration for Generate category/product URL Rewrites" stepKey="commentSetURLRewriteConfiguration" /> <comment userInput="Enable config to generate category/product URL Rewrites " stepKey="commentEnableConfig" /> <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="enableGenerateUrlRewrite"/> + <!-- Must create custom root category and assign to custom store to support cases where a test ran before this one that deleted the Default Category --> + <createData entity="NewRootCategory" stepKey="newRootCategory"/> <createData entity="NewRootCategory" stepKey="simpleSubCategory1"> - <field key="parent_id">2</field> + <field key="parent_id">$$newRootCategory.id$$</field> </createData> <createData entity="SubCategoryWithParent" stepKey="simpleSubCategory2"> <requiredEntity createDataKey="simpleSubCategory1"/> @@ -34,6 +36,27 @@ <createData entity="SimpleProductAfterImport1" stepKey="createSimpleProduct"> <requiredEntity createDataKey="simpleSubCategory3"/> </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{customWebsite.name}}"/> + <argument name="websiteCode" value="{{customWebsite.code}}"/> + </actionGroup> + <actionGroup ref="CreateCustomStoreActionGroup" stepKey="createCustomStore"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="$$newRootCategory.name$$"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToProduct"> + <argument name="productId" value="$$createSimpleProduct.id$$"/> + </actionGroup> + <actionGroup ref="AdminChangeWebSiteAssignedToProductActionGroup" stepKey="changeProductWebsite"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="websiteToDeselect" value="{{_defaultWebsite.name}}"/> + </actionGroup> </before> <after> <comment userInput="Delete all products that replaced products in the before block post import " stepKey="commentDeleteAllProducts" /> @@ -43,11 +66,15 @@ <deleteData createDataKey="simpleSubCategory1" stepKey="deleteSimpleSubCategory1"/> <comment userInput="Disable config to generate category/product URL Rewrites " stepKey="commentDisableConfig" /> <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="disableGenerateUrlRewrite"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + <deleteData createDataKey="newRootCategory" stepKey="deleteRootCategory"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> - <comment userInput="1. Log in to Admin " stepKey="commentAdminLogin" /> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <comment userInput="BIC workaround" stepKey="commentAdminLogin"/> + <comment userInput="BIC workaround" stepKey="loginAsAdmin"/> <comment userInput="2. Open Marketing - SEO and Search - URL Rewrites " stepKey="commentVerifyUrlRewrite" /> <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="amOnUrlRewriteIndexPage"/> diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index a59bc900f3fe7..32d63e0d46368 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -7,6 +7,7 @@ namespace Magento\UrlRewrite\Test\Unit\Controller; +use Laminas\Stdlib\Parameters; use Laminas\Stdlib\ParametersInterface; use Magento\Framework\App\Action\Forward; use Magento\Framework\App\Action\Redirect; @@ -89,7 +90,9 @@ protected function setUp(): void ->addMethods(['setRedirect']) ->onlyMethods(['sendResponse']) ->getMockForAbstractClass(); - $this->requestQuery = $this->createMock(ParametersInterface::class); + $this->requestQuery = $this->getMockBuilder(Parameters::class) + ->onlyMethods(['__serialize', '__unserialize']) + ->getMock(); $this->request = $this->getMockBuilder(Http::class) ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/UrlRewrite/composer.json b/app/code/Magento/UrlRewrite/composer.json index c18e1933798f3..6ecb6a79a73cf 100644 --- a/app/code/Magento/UrlRewrite/composer.json +++ b/app/code/Magento/UrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/UrlRewriteGraphQl/composer.json b/app/code/Magento/UrlRewriteGraphQl/composer.json index 63bfc619e5173..9ac2e471dce30 100644 --- a/app/code/Magento/UrlRewriteGraphQl/composer.json +++ b/app/code/Magento/UrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-url-rewrite": "*" }, diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php b/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php index 39ee382709e56..77ff12e3d60b6 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php @@ -25,27 +25,27 @@ class SaveRole extends \Magento\User\Controller\Adminhtml\User\Role implements H /** * Session keys for Info form data */ - const ROLE_EDIT_FORM_DATA_SESSION_KEY = 'role_edit_form_data'; + public const ROLE_EDIT_FORM_DATA_SESSION_KEY = 'role_edit_form_data'; /** * Session keys for Users form data */ - const IN_ROLE_USER_FORM_DATA_SESSION_KEY = 'in_role_user_form_data'; + public const IN_ROLE_USER_FORM_DATA_SESSION_KEY = 'in_role_user_form_data'; /** * Session keys for original Users form data */ - const IN_ROLE_OLD_USER_FORM_DATA_SESSION_KEY = 'in_role_old_user_form_data'; + public const IN_ROLE_OLD_USER_FORM_DATA_SESSION_KEY = 'in_role_old_user_form_data'; /** * Session keys for Use all resources flag form data */ - const RESOURCE_ALL_FORM_DATA_SESSION_KEY = 'resource_all_form_data'; + public const RESOURCE_ALL_FORM_DATA_SESSION_KEY = 'resource_all_form_data'; /** * Session keys for Resource form data */ - const RESOURCE_FORM_DATA_SESSION_KEY = 'resource_form_data'; + public const RESOURCE_FORM_DATA_SESSION_KEY = 'resource_form_data'; /** * @var SecurityCookie @@ -155,7 +155,7 @@ protected function validateUser() */ private function parseRequestVariable($paramName): array { - $value = $this->getRequest()->getParam($paramName, null); + $value = $this->getRequest()->getParam($paramName, ''); // phpcs:ignore Magento2.Functions.DiscouragedFunction parse_str($value, $value); $value = array_keys($value); diff --git a/app/code/Magento/User/composer.json b/app/code/Magento/User/composer.json index 794be4aaf3adb..5a9474cbf0231 100644 --- a/app/code/Magento/User/composer.json +++ b/app/code/Magento/User/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Usps/composer.json b/app/code/Magento/Usps/composer.json index 9a27c0b9096b7..a8435a8c90998 100644 --- a/app/code/Magento/Usps/composer.json +++ b/app/code/Magento/Usps/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Variable/composer.json b/app/code/Magento/Variable/composer.json index a04e8bde18fbc..529ef24fef17d 100644 --- a/app/code/Magento/Variable/composer.json +++ b/app/code/Magento/Variable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-store": "*", diff --git a/app/code/Magento/Variable/view/adminhtml/web/variables.js b/app/code/Magento/Variable/view/adminhtml/web/variables.js index 8c91b925be6d0..83580189bb648 100644 --- a/app/code/Magento/Variable/view/adminhtml/web/variables.js +++ b/app/code/Magento/Variable/view/adminhtml/web/variables.js @@ -301,7 +301,7 @@ define([ updateElementAtCursor(textareaElm, value); textareaElm.focus(); textareaElm.scrollTop = scrollPos; - jQuery(textareaElm).change(); + jQuery(textareaElm).trigger('change'); textareaElm = null; } diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index 9a82b04b5e2dc..ff537342fb8c3 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/VaultGraphQl/composer.json b/app/code/Magento/VaultGraphQl/composer.json index 8d39dcccce3b0..b2e60772cf1af 100644 --- a/app/code/Magento/VaultGraphQl/composer.json +++ b/app/code/Magento/VaultGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-vault": "*", "magento/module-graph-ql": "*" diff --git a/app/code/Magento/Version/composer.json b/app/code/Magento/Version/composer.json index c4c8a82d5889e..50561cbdea0a9 100644 --- a/app/code/Magento/Version/composer.json +++ b/app/code/Magento/Version/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/Webapi/composer.json b/app/code/Magento/Webapi/composer.json index 28679acc6b5f4..c7f92f627a9c0 100644 --- a/app/code/Magento/Webapi/composer.json +++ b/app/code/Magento/Webapi/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Webapi/etc/adminhtml/system.xml b/app/code/Magento/Webapi/etc/adminhtml/system.xml index d6b85994b8603..ae19d774a43bb 100644 --- a/app/code/Magento/Webapi/etc/adminhtml/system.xml +++ b/app/code/Magento/Webapi/etc/adminhtml/system.xml @@ -20,6 +20,31 @@ <comment>If empty, UTF-8 will be used.</comment> </field> </group> + <group id="validation" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Web Api Input Limits</label> + <field id="input_limit_enabled" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <label>Enable Input Limits</label> + </field> + <field id="complex_array_limit" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Input List Limit</label> + <comment>Maximum number of items allowed in an entity's array property.</comment> + <depends> + <field id="input_limit_enabled">1</field> + </depends> + </field> + <field id="maximum_page_size" translate="label comment" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Maximum Page Size</label> + <comment>Maximum number of items allowed in a paginated search result.</comment> + <depends> + <field id="input_limit_enabled">1</field> + </depends> + </field> + <field id="default_page_size" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Default Page Size</label> + <comment>Default number of items a paginated search result.</comment> + </field> + </group> </section> </system> </config> diff --git a/app/code/Magento/WebapiAsync/composer.json b/app/code/Magento/WebapiAsync/composer.json index 24875afa93a63..e8e74882fc3e6 100644 --- a/app/code/Magento/WebapiAsync/composer.json +++ b/app/code/Magento/WebapiAsync/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/framework-message-queue": "*", "magento/module-webapi": "*", diff --git a/app/code/Magento/WebapiSecurity/composer.json b/app/code/Magento/WebapiSecurity/composer.json index 99c006011a448..65ac82169d0bb 100644 --- a/app/code/Magento/WebapiSecurity/composer.json +++ b/app/code/Magento/WebapiSecurity/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-webapi": "*" }, diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontBundleWithTwoOptionsWithWeeeAddToCartTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontBundleWithTwoOptionsWithWeeeAddToCartTest.xml index 546f085b3549f..453a5d6ad76ac 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontBundleWithTwoOptionsWithWeeeAddToCartTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontBundleWithTwoOptionsWithWeeeAddToCartTest.xml @@ -21,13 +21,16 @@ </annotations> <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminTaxRateGridOpenPageActionGroup" stepKey="navigateToTaxRatesPage"/> + <actionGroup ref="AdminDeleteMultipleTaxRatesActionGroup" stepKey="deleteAllNonDefaultTaxRates"/> <createData entity="WeeeConfigEnable" stepKey="enableFPT"/> - <magentoCLI command="config:set tax/weee/enable 1" stepKey="EnableWeee"/> - <magentoCLI command="config:set tax/weee/display_list 0" stepKey="EnableDisplayInListFPTOnly"/> - <magentoCLI command="config:set tax/weee/display 0" stepKey="SetDisplayFPTOnly"/> - <magentoCLI command="config:set tax/weee/display_sales 0" stepKey="SetDisplaySalesFPTOnly"/> - <magentoCLI command="config:set tax/weee/display_email 0" stepKey="SetDisplayEmailFPTOnly"/> - <magentoCLI command="config:set tax/weee/apply_vat 1" stepKey="EnableApplyVAT"/> + <comment userInput="BIC workaround" stepKey="EnableWeee"/> + <comment userInput="BIC workaround" stepKey="EnableDisplayInListFPTOnly"/> + <comment userInput="BIC workaround" stepKey="SetDisplayFPTOnly"/> + <comment userInput="BIC workaround" stepKey="SetDisplaySalesFPTOnly"/> + <comment userInput="BIC workaround" stepKey="SetDisplayEmailFPTOnly"/> + <comment userInput="BIC workaround" stepKey="EnableApplyVAT"/> <magentoCLI command="config:set tax/weee/include_in_subtotal 1" stepKey="EnableIncludeInSubtotal"/> <createData entity="FPTProductAttribute" stepKey="createProductFPTAttribute"/> @@ -40,9 +43,10 @@ </before> <after> - <deleteData createDataKey="createFullTaxRate" stepKey="deleteFullTaxRate"/> - <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> <createData entity="WeeeConfigDisable" stepKey="disableFPT"/> + <magentoCLI command="config:set tax/weee/include_in_subtotal 0" stepKey="disableIncludeInSubtotal"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <deleteData createDataKey="createFullTaxRate" stepKey="deleteFullTaxRate"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteBundle"> @@ -50,7 +54,7 @@ </actionGroup> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <comment userInput="BIC workaround" stepKey="loginAsAdmin"/> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openSimpleProduct1EditPage"> <argument name="productId" value="$simpleProduct1.id$"/> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index 833f619888bfb..3139a761fac36 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -24,6 +24,9 @@ <before> <!-- Preconditions --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminTaxRateGridOpenPageActionGroup" stepKey="navigateToTaxRatesPage"/> + <actionGroup ref="AdminDeleteMultipleTaxRatesActionGroup" stepKey="deleteAllNonDefaultTaxRates"/> <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> <!-- Fixed Product Tax attribute is created and added to default attribute set --> @@ -41,7 +44,6 @@ </createData> <!-- Customer is created with default addresses: --> <createData entity="Simple_US_Customer_CA" stepKey="createCustomer"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductEditPage"> <argument name="productId" value="$createSimpleProduct.id$"/> </actionGroup> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index 8e8667cb7e13d..bb6fe082f4540 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -24,6 +24,9 @@ <before> <!-- Preconditions --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminTaxRateGridOpenPageActionGroup" stepKey="navigateToTaxRatesPage"/> + <actionGroup ref="AdminDeleteMultipleTaxRatesActionGroup" stepKey="deleteAllNonDefaultTaxRates"/> <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> <!-- Fixed Product Tax attribute is created and added to default attribute set --> @@ -40,7 +43,6 @@ <!-- Customer is created with default addresses: --> <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 3a3f9c7e8931a..a846f0c665d92 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -24,6 +24,9 @@ <before> <!-- Preconditions --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminTaxRateGridOpenPageActionGroup" stepKey="navigateToTaxRatesPage"/> + <actionGroup ref="AdminDeleteMultipleTaxRatesActionGroup" stepKey="deleteAllNonDefaultTaxRates"/> <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> <!-- Fixed Product Tax attribute is created and added to default attribute set --> @@ -39,7 +42,6 @@ <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> <field key="price">10.00</field> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductEditPage"> <argument name="productId" value="$createSimpleProduct.id$"/> </actionGroup> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index 0d54991f84395..fe52d6bc0c015 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -24,6 +24,9 @@ <before> <!-- Preconditions --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminTaxRateGridOpenPageActionGroup" stepKey="navigateToTaxRatesPage"/> + <actionGroup ref="AdminDeleteMultipleTaxRatesActionGroup" stepKey="deleteAllNonDefaultTaxRates"/> <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> <!-- Fixed Product Tax attribute is created and added to default attribute set --> @@ -38,7 +41,6 @@ <field key="price">40.00</field> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> diff --git a/app/code/Magento/Weee/composer.json b/app/code/Magento/Weee/composer.json index d363aa5a88b7d..17ea348406e96 100644 --- a/app/code/Magento/Weee/composer.json +++ b/app/code/Magento/Weee/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/WeeeGraphQl/composer.json b/app/code/Magento/WeeeGraphQl/composer.json index 31019f3efc2b2..8a199e108bc82 100644 --- a/app/code/Magento/WeeeGraphQl/composer.json +++ b/app/code/Magento/WeeeGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-tax": "*", diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php index 32bae10c801c8..aac105b96028b 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php @@ -172,7 +172,7 @@ protected function _addField($parameter) } } else { // phpcs:ignore Magento2.Functions.DiscouragedFunction - $data['value'] = html_entity_decode($data['value']); + $data['value'] = \is_string($data['value']) ? html_entity_decode($data['value']) : ''; } // prepare element dropdown values diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php index e7454faf925a6..6c371d3e83354 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -13,11 +12,9 @@ class Index extends \Magento\Backend\App\Action implements HttpPostActionInterfa /** * Authorization level of a basic admin session */ - const ADMIN_RESOURCE = 'Magento_Widget::widget_instance'; + public const ADMIN_RESOURCE = 'Magento_Widget::widget_instance'; /** - * Core registry - * * @var \Magento\Framework\Registry */ protected $_coreRegistry; @@ -50,11 +47,11 @@ public function __construct( public function execute() { // save extra params for widgets insertion form - $skipped = $this->getRequest()->getParam('skip_widgets'); + $skipped = $this->getRequest()->getParam('skip_widgets', ''); $skipped = $this->_widgetConfig->decodeWidgetsFromQuery($skipped); - $this->_coreRegistry->register('skip_widgets', $skipped); + // phpcs:ignore Magento2.Legacy.ObsoleteResponse $this->_view->loadLayout('empty')->renderLayout(); } } diff --git a/app/code/Magento/Widget/Model/ResourceModel/Widget/Instance.php b/app/code/Magento/Widget/Model/ResourceModel/Widget/Instance.php index 9a6a736a8edec..78b86009dede3 100644 --- a/app/code/Magento/Widget/Model/ResourceModel/Widget/Instance.php +++ b/app/code/Magento/Widget/Model/ResourceModel/Widget/Instance.php @@ -89,6 +89,7 @@ protected function _afterSave(AbstractModel $object) if (in_array($pageGroup['page_id'], $pageIds)) { $connection->update($pageTable, $data, ['page_id = ?' => (int)$pageId]); } else { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $connection->insert($pageTable, array_merge(['instance_id' => $object->getId()], $data)); $pageId = $connection->lastInsertId($pageTable); } @@ -124,7 +125,7 @@ protected function _saveLayoutUpdates($widgetInstance, $pageGroupData) $pageGroupData['template'] ); $insert = ['handle' => $handle, 'xml' => $xml]; - if (strlen($widgetInstance->getSortOrder())) { + if ($widgetInstance->getSortOrder() !== null && strlen($widgetInstance->getSortOrder())) { $insert['sort_order'] = $widgetInstance->getSortOrder(); } @@ -147,6 +148,7 @@ protected function _saveLayoutUpdates($widgetInstance, $pageGroupData) /** * Prepare store ids. + * * If one of store id is default (0) return all store ids * * @param array $storeIds @@ -162,6 +164,7 @@ protected function _prepareStoreIds($storeIds) /** * Perform actions before object delete. + * * Collect page ids and layout update ids and set to object for further delete * * @param \Magento\Framework\Model\AbstractModel $object @@ -188,6 +191,7 @@ protected function _beforeDelete(AbstractModel $object) /** * Perform actions after object delete. + * * Delete layout updates by layout update ids collected in _beforeSave * * @param \Magento\Widget\Model\Widget\Instance $object diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminFillSpecificPageWidgetMainFieldsActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminFillSpecificPageWidgetMainFieldsActionGroup.xml index c0fa53da7c688..928f614454a80 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminFillSpecificPageWidgetMainFieldsActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminFillSpecificPageWidgetMainFieldsActionGroup.xml @@ -27,9 +27,11 @@ <selectOption selector="{{AdminNewWidgetSection.displayOnByIndex(index)}}" userInput="{{widget.display_on}}" stepKey="setDisplayOn"/> <waitForPageLoad stepKey="waitForDisplayOnChangesApplied"/> <selectOption selector="{{AdminNewWidgetSection.layoutByIndex(index)}}" userInput="{{widget.page}}" stepKey="selectPage"/> + <waitForPageLoad stepKey="waitForPageChangesApplied"/> <selectOption selector="{{AdminNewWidgetSection.templateByIndex(index)}}" userInput="{{widget.template}}" stepKey="selectTemplate"/> - <scrollTo selector="{{AdminNewWidgetSection.containerByIndex(index)}}" stepKey="scrollToSelectContainerElement"/> - <waitForPageLoad stepKey="waitForScroll"/> + <waitForPageLoad stepKey="waitForTemplateChangesApplied"/> + <comment userInput="BIC workaround" stepKey="scrollToSelectContainerElement"/> + <comment userInput="BIC workaround" stepKey="waitForScroll"/> <selectOption selector="{{AdminNewWidgetSection.containerByIndex(index)}}" userInput="{{widget.container}}" stepKey="setContainer"/> <waitForPageLoad stepKey="waitForContainerChangesApplied"/> </actionGroup> diff --git a/app/code/Magento/Widget/composer.json b/app/code/Magento/Widget/composer.json index 83b909cb447d7..1a9c1c816d8f1 100644 --- a/app/code/Magento/Widget/composer.json +++ b/app/code/Magento/Widget/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddCategoryProductToWishlistActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddCategoryProductToWishlistActionGroup.xml index 336841901a7ec..26f5f75075d48 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddCategoryProductToWishlistActionGroup.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerAddCategoryProductToWishlistActionGroup.xml @@ -17,9 +17,11 @@ </arguments> <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(productVar.name)}}" stepKey="addCategoryProductToWishlistMoveMouseOverProduct"/> + <waitForElementVisible selector="{{StorefrontCategoryProductSection.ProductAddToWishlistByName(productVar.name)}}" stepKey="waitForAddCategoryProductToWishlist"/> <click selector="{{StorefrontCategoryProductSection.ProductAddToWishlistByName(productVar.name)}}" stepKey="addCategoryProductToWishlistClickAddProductToWishlist"/> + <waitForPageLoad stepKey="waitForPageLoad"/> <waitForElement selector="{{StorefrontCustomerWishlistSection.successMsg}}" time="30" stepKey="addCategoryProductToWishlistWaitForSuccessMessage"/> - <see selector="{{StorefrontCustomerWishlistSection.successMsg}}" userInput="{{productVar.name}} has been added to your Wish List." stepKey="addCategoryProductToWishlistSeeProductNameAddedToWishlist"/> + <waitForText selector="{{StorefrontCustomerWishlistSection.successMsg}}" userInput="{{productVar.name}} has been added to your Wish List." stepKey="addCategoryProductToWishlistSeeProductNameAddedToWishlist"/> <seeCurrentUrlMatches regex="~/wishlist_id/\d+/$~" stepKey="seeCurrentUrlMatches"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerCheckProductInWishlistSidebarActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerCheckProductInWishlistSidebarActionGroup.xml index 04277897c0cb2..f7da73bd4baa9 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerCheckProductInWishlistSidebarActionGroup.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerCheckProductInWishlistSidebarActionGroup.xml @@ -17,8 +17,8 @@ </arguments> <waitForElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductTitleByName(productVar.name)}}" time="30" stepKey="assertWishlistSidebarProductName"/> - <see userInput="${{productVar.price}}.00" selector="{{StorefrontCustomerWishlistSidebarSection.ProductPriceByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductPrice"/> - <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistSidebarAddToCart"/> - <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductImage"/> + <waitForText userInput="${{productVar.price}}.00" selector="{{StorefrontCustomerWishlistSidebarSection.ProductPriceByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductPrice"/> + <waitForElementVisible selector="{{StorefrontCustomerWishlistSidebarSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistSidebarAddToCart"/> + <waitForElementVisible selector="{{StorefrontCustomerWishlistSidebarSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductImage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml index 87021cfbbbce1..3a1b30a1e7a42 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml @@ -46,6 +46,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clickClearFilters"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml index 8983bb4cc9b76..87e39972cd974 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml @@ -30,6 +30,13 @@ <createData entity="Simple_US_Customer" stepKey="customer"/> </before> <after> + <actionGroup ref="AdminOpenSalesCheckoutConfigPageActionGroup" stepKey="openSalesCheckoutCartConfig1"> + <argument name="tabGroupAnchor" value="#checkout_cart-link"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValueForAllowedCurrency"> + <argument name="rowId" value="row_checkout_cart_configurable_product_image"/> + </actionGroup> + <actionGroup ref="SaveStoreConfigurationActionGroup" stepKey="saveStoreConfiguration"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPage"/> <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearGridFiltersConfigurable"/> @@ -55,6 +62,9 @@ <argument name="sku" value="{{_defaultProduct.sku}}"/> </actionGroup> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> + <actionGroup ref="AdminDeleteProductAttributeByLabelActionGroup" stepKey="deleteProductAttribute"> + <argument name="productAttributeLabel" value="{{colorProductAttribute.default_label}}"/> + </actionGroup> <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml index 8fe3d3c707900..59a77506c00ee 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml @@ -55,11 +55,9 @@ </after> <!-- Change products visibility on store-view level --> - <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct1"> - <argument name="product" value="$$product$$"/> - </actionGroup> - <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct1"> - <argument name="product" value="$$product$$"/> + <comment userInput="BIC workaround" stepKey="searchForProduct1"/> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openEditProduct1"> + <argument name="productId" value="$$product.id$$"/> </actionGroup> <scrollToTopOfPage stepKey="scrollToTopToChangeStore"/> <click selector="{{AdminProductFormActionSection.changeStoreButton}}" stepKey="clickSwitchStoreMenuForProduct1"/> @@ -69,12 +67,10 @@ <click selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="acceptStoreSwitchingForProduct1"/> <click selector="{{AdminProductFormSection.visibilityUseDefault}}" stepKey="uncheckVisibilityUseDefaultValueForProduct1"/> <selectOption userInput="Not Visible Individually" selector="{{AdminProductFormSection.visibility}}" stepKey="makeProductNotVisibleOnSecondaryStoreView"/> - <actionGroup ref="AdminProductFormSaveActionGroup" stepKey="saveEditedProductForProduct1"/> - <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct2"> - <argument name="product" value="$$secondProduct$$"/> - </actionGroup> - <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct2"> - <argument name="product" value="$$secondProduct$$"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveEditedProductForProduct1"/> + <comment userInput="BIC workaround" stepKey="searchForProduct2"/> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openEditProduct2"> + <argument name="productId" value="$$secondProduct.id$$"/> </actionGroup> <scrollToTopOfPage stepKey="scrollToTopToChangeStoreAgain"/> <click selector="{{AdminProductFormActionSection.changeStoreButton}}" stepKey="clickSwitchStoreMenuForProduct2"/> @@ -84,7 +80,7 @@ <click selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="acceptStoreSwitchingForProduct2"/> <click selector="{{AdminProductFormSection.visibilityUseDefault}}" stepKey="uncheckVisibilityUseDefaultValueForProduct2"/> <selectOption userInput="Not Visible Individually" selector="{{AdminProductFormSection.visibility}}" stepKey="makeProductNotVisibleOnDefaultStoreView"/> - <actionGroup ref="AdminProductFormSaveActionGroup" stepKey="saveEditedProductForProduct2"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveEditedProductForProduct2"/> <!-- Sign in as customer --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> @@ -94,19 +90,27 @@ <see userInput="$$customer.lastname$$" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" stepKey="seeLastName"/> <see userInput="$$customer.email$$" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" stepKey="seeEmail"/> <!-- Add product visible on default store to wishlist --> - <amOnPage url="{{StorefrontProductPage.url($$product.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPageOnDefaultStore"/> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="navigateToProductPageOnDefaultStore"> + <argument name="product" value="$product$"/> + </actionGroup> <see userInput="$$product.name$$" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertFirstProductNameTitle"/> <click selector="{{StorefrontProductPageSection.addToWishlist}}" stepKey="addFirstProductToWishlist"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="$$product.name$$" selector="{{StorefrontCustomerWishlistSection.productItemNameText}}" stepKey="seeProduct1InWishlistOnSecondStore"/> <!-- Switch to second store and add second product (visible on second store) to wishlist --> <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnDefaultStore"/> <click selector="{{StorefrontFooterSection.storeLink($$storeGroup.group[name]$$)}}" stepKey="selectSecondStoreToSwitchOn"/> <!-- Verify that both products are visible in wishlist on both stores --> - <amOnPage url="{{StorefrontProductPage.url($$secondProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPageOnSecondStore"/> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="navigateToProductPageOnSecondStore"> + <argument name="product" value="$secondProduct$"/> + </actionGroup> <see userInput="$$secondProduct.name$$" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertSecondProductNameTitle"/> <click selector="{{StorefrontProductPageSection.addToWishlist}}" stepKey="addSecondProductToWishlist"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> <see userInput="$$secondProduct.name$$" selector="{{StorefrontCustomerWishlistSection.productItemNameText}}" stepKey="seeProduct2InWishlistOnSecondStore"/> <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnSecondStore"/> <click selector="{{StorefrontFooterSection.storeLink('Main Website Store')}}" stepKey="selectDefaultStoreToSwitchOn"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> <see userInput="$$product.name$$" selector="{{StorefrontCustomerWishlistSection.productItemNameText}}" stepKey="seeProduct1InWishlistOnDefaultStore"/> </test> </tests> diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index 33f52e9c5a629..f212289b71940 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json index 5e7a77ce75138..88be4c5a888e5 100644 --- a/app/code/Magento/WishlistAnalytics/composer.json +++ b/app/code/Magento/WishlistAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-wishlist-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-wishlist": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/WishlistGraphQl/composer.json b/app/code/Magento/WishlistGraphQl/composer.json index c45e5b495ee3b..4c007b8c7124e 100755 --- a/app/code/Magento/WishlistGraphQl/composer.json +++ b/app/code/Magento/WishlistGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/module-wishlist": "*", "magento/module-store": "*", diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less index 29a7499ec72f4..7486fa76078e9 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less @@ -53,6 +53,10 @@ // Utilities // --------------------------------------------- +/** + * @codingStandardsIgnoreStart + */ + .admin__menu-link() { color: @menu-item__color; display: block; @@ -520,7 +524,6 @@ } // This part hides Submenu Group Titles only for menus with single groups. - .submenu .column:only-of-type .submenu-group-title, .level-0 > .submenu > ul > .level-1:only-of-type > .submenu-group-title { display: none; } diff --git a/app/design/adminhtml/Magento/backend/composer.json b/app/design/adminhtml/Magento/backend/composer.json index 40ab57dc13f12..7de6c1e5b46b1 100644 --- a/app/design/adminhtml/Magento/backend/composer.json +++ b/app/design/adminhtml/Magento/backend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-theme", diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less index 3ed4ac91d32a4..1a5e3f8f4f80b 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less @@ -403,3 +403,7 @@ margin: 0; padding: 0; } + +.ui-timepicker-div .ui_tpicker_unit_hide { + display:none; +} diff --git a/app/design/adminhtml/Magento/backend/web/js/theme.js b/app/design/adminhtml/Magento/backend/web/js/theme.js index e3fca76a7ad1a..b6919f2d463ee 100644 --- a/app/design/adminhtml/Magento/backend/web/js/theme.js +++ b/app/design/adminhtml/Magento/backend/web/js/theme.js @@ -594,13 +594,13 @@ define('collapsable', [ var self = this; this.element - .on('show', function (e) { + .on('show.bs.collapse', function (e) { var fieldsetWrapper = $(this).closest(self.options.wrapper); fieldsetWrapper.addClass(self.options.openedClass); e.stopPropagation(); }) - .on('hide', function (e) { + .on('hide.bs.collapse', function (e) { var fieldsetWrapper = $(this).closest(self.options.wrapper); fieldsetWrapper.removeClass(self.options.openedClass); diff --git a/app/design/frontend/Magento/blank/composer.json b/app/design/frontend/Magento/blank/composer.json index 200365edeed39..81656f8c80863 100644 --- a/app/design/frontend/Magento/blank/composer.json +++ b/app/design/frontend/Magento/blank/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*" }, "type": "magento2-theme", diff --git a/app/design/frontend/Magento/luma/composer.json b/app/design/frontend/Magento/luma/composer.json index b860eb01ca8c0..d7d17bb382fc6 100644 --- a/app/design/frontend/Magento/luma/composer.json +++ b/app/design/frontend/Magento/luma/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "magento/framework": "*", "magento/theme-frontend-blank": "*" }, diff --git a/composer.json b/composer.json index 92e0825825fbb..221343f1c2e7b 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", @@ -34,7 +34,7 @@ "colinmollenhour/credis": "1.12.1", "colinmollenhour/php-redis-session-abstract": "~1.4.5", "composer/composer": "^1.9 || ^2.0", - "elasticsearch/elasticsearch": "~7.15.0", + "elasticsearch/elasticsearch": "~7.16.0", "guzzlehttp/guzzle": "^7.3.0", "laminas/laminas-captcha": "^2.11", "laminas/laminas-code": "~4.5.0", @@ -63,8 +63,8 @@ "league/flysystem-aws-s3-v3": "^2.0", "magento/composer": "1.8.x-dev as 1.8.0", "magento/composer-dependency-version-audit-plugin": "~0.1", - "magento/magento-composer-installer": ">=0.1.11", - "magento/zendframework1": "dev-master as 1.14.6", + "magento/magento-composer-installer": "0.3.*@beta", + "magento/zendframework1": "1.14.6-beta2 as 1.14.6", "monolog/monolog": "^2.3", "pelago/emogrifier": "^6.0.0", "php-amqplib/php-amqplib": "~3.1.0", diff --git a/composer.lock b/composer.lock index 6b8c2b6f2dd1c..90e8c78101e87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c9701e5f24240f0eca86cdadf3bae43", + "content-hash": "924fe0d5154aa2937a1fd4d9c1403a61", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.208.2", + "version": "3.208.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "ad35b407046b8eb3856b78795371f1fde46769f4" + "reference": "58fa9d8b522b0afa260299179ff950c783ff0ee1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ad35b407046b8eb3856b78795371f1fde46769f4", - "reference": "ad35b407046b8eb3856b78795371f1fde46769f4", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/58fa9d8b522b0afa260299179ff950c783ff0ee1", + "reference": "58fa9d8b522b0afa260299179ff950c783ff0ee1", "shasum": "" }, "require": { @@ -143,9 +143,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.208.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.208.5" }, - "time": "2021-12-06T19:15:55+00:00" + "time": "2021-12-13T20:17:56+00:00" }, { "name": "brick/math", @@ -658,6 +658,77 @@ ], "time": "2021-04-07T13:37:33+00:00" }, + { + "name": "composer/pcre", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2", + "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-12-06T15:17:27+00:00" + }, { "name": "composer/semver", "version": "3.2.6", @@ -821,25 +892,27 @@ }, { "name": "composer/xdebug-handler", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339" + "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6555461e76962fd0379c444c46fd558a0fcfb65e", + "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e", "shasum": "" }, "require": { + "composer/pcre": "^1", "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" }, "type": "library", "autoload": { @@ -865,7 +938,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.2" + "source": "https://github.com/composer/xdebug-handler/tree/2.0.3" }, "funding": [ { @@ -881,7 +954,7 @@ "type": "tidelift" } ], - "time": "2021-07-31T17:03:58+00:00" + "time": "2021-12-08T13:07:32+00:00" }, { "name": "container-interop/container-interop", @@ -921,16 +994,16 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v7.15.0", + "version": "v7.16.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "77a4ade87aef8e8e6b84bafb6704cd35ac15742a" + "reference": "f87f93f71f564d4bbdc5f008d296d1c37d828e10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/77a4ade87aef8e8e6b84bafb6704cd35ac15742a", - "reference": "77a4ade87aef8e8e6b84bafb6704cd35ac15742a", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/f87f93f71f564d4bbdc5f008d296d1c37d828e10", + "reference": "f87f93f71f564d4bbdc5f008d296d1c37d828e10", "shasum": "" }, "require": { @@ -963,7 +1036,8 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "Apache-2.0", + "LGPL-2.1-only" ], "authors": [ { @@ -981,9 +1055,9 @@ ], "support": { "issues": "https://github.com/elastic/elasticsearch-php/issues", - "source": "https://github.com/elastic/elasticsearch-php/tree/v7.15.0" + "source": "https://github.com/elastic/elasticsearch-php/tree/v7.16.0" }, - "time": "2021-09-23T07:05:35+00:00" + "time": "2021-12-09T20:04:01+00:00" }, { "name": "ezimuel/guzzlestreams", @@ -1097,24 +1171,24 @@ }, { "name": "fgrosse/phpasn1", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e" + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/20299033c35f4300eb656e7e8e88cf52d1d6694e", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296", + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0" }, "require-dev": { - "phpunit/phpunit": "~6.3", - "satooshi/php-coveralls": "~2.0" + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^6.3 || ^7.0 || ^8.0" }, "suggest": { "ext-bcmath": "BCmath is the fallback extension for big integer calculations", @@ -1166,9 +1240,9 @@ ], "support": { "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.3.0" + "source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0" }, - "time": "2021-04-24T19:01:55+00:00" + "time": "2021-12-11T12:41:06+00:00" }, { "name": "guzzlehttp/guzzle", @@ -2098,16 +2172,16 @@ }, { "name": "laminas/laminas-feed", - "version": "2.15.0", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-feed.git", - "reference": "3ef837a12833c74b438d2c3780023c4244e0abae" + "reference": "cbd0e10c867a1efa6594164d229d8caf4a4ae4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/3ef837a12833c74b438d2c3780023c4244e0abae", - "reference": "3ef837a12833c74b438d2c3780023c4244e0abae", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/cbd0e10c867a1efa6594164d229d8caf4a4ae4c7", + "reference": "cbd0e10c867a1efa6594164d229d8caf4a4ae4c7", "shasum": "" }, "require": { @@ -2171,7 +2245,7 @@ "type": "community_bridge" } ], - "time": "2021-09-20T18:11:11+00:00" + "time": "2021-12-17T09:12:35+00:00" }, { "name": "laminas/laminas-http", @@ -3086,16 +3160,16 @@ }, { "name": "laminas/laminas-stdlib", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "db581851a092246ad99e12d4fddf105184924c71" + "reference": "6fe0842909638ca6bea8401b7e8168fb154bffb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/db581851a092246ad99e12d4fddf105184924c71", - "reference": "db581851a092246ad99e12d4fddf105184924c71", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/6fe0842909638ca6bea8401b7e8168fb154bffb5", + "reference": "6fe0842909638ca6bea8401b7e8168fb154bffb5", "shasum": "" }, "require": { @@ -3141,7 +3215,7 @@ "type": "community_bridge" } ], - "time": "2021-11-10T11:33:52+00:00" + "time": "2021-12-07T21:06:58+00:00" }, { "name": "laminas/laminas-text", @@ -3795,16 +3869,16 @@ }, { "name": "magento/magento-composer-installer", - "version": "0.2.1", + "version": "0.3.0-beta.1", "source": { "type": "git", "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "b9f929f718ef93ed61b6410bad85d40c37fd5ed3" + "reference": "0c1987b1ba4c8bacde15cad86f4dace1e3957104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/b9f929f718ef93ed61b6410bad85d40c37fd5ed3", - "reference": "b9f929f718ef93ed61b6410bad85d40c37fd5ed3", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/0c1987b1ba4c8bacde15cad86f4dace1e3957104", + "reference": "0c1987b1ba4c8bacde15cad86f4dace1e3957104", "shasum": "" }, "require": { @@ -3815,12 +3889,10 @@ "magento-hackathon/magento-composer-installer": "*" }, "require-dev": { - "firegento/phpcs": "~1.1.0", "mikey179/vfsstream": "*", - "phpunit/phpunit": "*", - "phpunit/phpunit-mock-objects": "dev-master", - "squizlabs/php_codesniffer": "1.4.7", - "symfony/process": "*" + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "~3.6.1", + "symfony/process": "~5.4.0" }, "type": "composer-plugin", "extra": { @@ -3871,22 +3943,22 @@ "magento" ], "support": { - "source": "https://github.com/magento/magento-composer-installer/tree/0.2.1" + "source": "https://github.com/magento/magento-composer-installer/tree/0.3.0-beta.1" }, - "time": "2021-03-04T20:05:10+00:00" + "time": "2021-12-17T20:04:15+00:00" }, { "name": "magento/zendframework1", - "version": "dev-master", + "version": "1.14.6-beta2", "source": { "type": "git", "url": "https://github.com/magento/zf1.git", - "reference": "83f34f56344740c94138f4804870e33c4fd56508" + "reference": "7b4cb1f74a2515c16113812543f47742207b4a7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/83f34f56344740c94138f4804870e33c4fd56508", - "reference": "83f34f56344740c94138f4804870e33c4fd56508", + "url": "https://api.github.com/repos/magento/zf1/zipball/7b4cb1f74a2515c16113812543f47742207b4a7f", + "reference": "7b4cb1f74a2515c16113812543f47742207b4a7f", "shasum": "" }, "require": { @@ -3896,7 +3968,6 @@ "phpunit/dbunit": "1.3.*", "phpunit/phpunit": "3.7.*" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3923,9 +3994,9 @@ ], "support": { "issues": "https://github.com/magento/zf1/issues", - "source": "https://github.com/magento/zf1/tree/master" + "source": "https://github.com/magento/zf1/tree/1.14.6-beta2" }, - "time": "2021-11-30T15:38:43+00:00" + "time": "2021-12-10T22:41:02+00:00" }, { "name": "monolog/monolog", @@ -4340,16 +4411,16 @@ }, { "name": "php-amqplib/php-amqplib", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/php-amqplib/php-amqplib.git", - "reference": "c8812f783fe8714a8a8e387003fb297aded7f974" + "reference": "1d8e724c1de8ac5a4322f26ba735695880514874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/c8812f783fe8714a8a8e387003fb297aded7f974", - "reference": "c8812f783fe8714a8a8e387003fb297aded7f974", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/1d8e724c1de8ac5a4322f26ba735695880514874", + "reference": "1d8e724c1de8ac5a4322f26ba735695880514874", "shasum": "" }, "require": { @@ -4415,9 +4486,9 @@ ], "support": { "issues": "https://github.com/php-amqplib/php-amqplib/issues", - "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.1.0" + "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.1.1" }, - "time": "2021-10-22T16:37:06+00:00" + "time": "2021-12-03T14:55:21+00:00" }, { "name": "phpseclib/mcrypt_compat", @@ -5179,29 +5250,33 @@ }, { "name": "sabberworm/php-css-parser", - "version": "8.3.1", + "version": "8.4.0", "source": { "type": "git", "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "d217848e1396ef962fb1997cf3e2421acba7f796" + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/d217848e1396ef962fb1997cf3e2421acba7f796", - "reference": "d217848e1396ef962fb1997cf3e2421acba7f796", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", "shasum": "" }, "require": { - "php": ">=5.3.2" + "ext-iconv": "*", + "php": ">=5.6.20" }, "require-dev": { "codacy/coverage": "^1.4", - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^4.8.36" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" }, "type": "library", "autoload": { - "psr-0": { - "Sabberworm\\CSS": "lib/" + "psr-4": { + "Sabberworm\\CSS\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5214,7 +5289,7 @@ } ], "description": "Parser for CSS Files written in PHP", - "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "keywords": [ "css", "parser", @@ -5222,9 +5297,9 @@ ], "support": { "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", - "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.3.1" + "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" }, - "time": "2020-06-01T09:10:00+00:00" + "time": "2021-12-11T13:40:54+00:00" }, { "name": "seld/jsonlint", @@ -5291,16 +5366,16 @@ }, { "name": "seld/phar-utils", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0" + "reference": "9f3452c93ff423469c0d56450431562ca423dcee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/749042a2315705d2dfbbc59234dd9ceb22bf3ff0", - "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/9f3452c93ff423469c0d56450431562ca423dcee", + "reference": "9f3452c93ff423469c0d56450431562ca423dcee", "shasum": "" }, "require": { @@ -5333,9 +5408,9 @@ ], "support": { "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/1.1.2" + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.0" }, - "time": "2021-08-19T21:01:38+00:00" + "time": "2021-12-10T11:20:11+00:00" }, { "name": "spomky-labs/aes-key-wrap", @@ -5777,16 +5852,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v5.4.0", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "69c398723857bb19fdea78496cedea0f756decab" + "reference": "9bd1ef389a2fe05fea7306b6155403e8a960d73d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/69c398723857bb19fdea78496cedea0f756decab", - "reference": "69c398723857bb19fdea78496cedea0f756decab", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9bd1ef389a2fe05fea7306b6155403e8a960d73d", + "reference": "9bd1ef389a2fe05fea7306b6155403e8a960d73d", "shasum": "" }, "require": { @@ -5846,7 +5921,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.0" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.1" }, "funding": [ { @@ -5862,7 +5937,7 @@ "type": "tidelift" } ], - "time": "2021-11-29T15:30:56+00:00" + "time": "2021-12-01T16:25:34+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6369,16 +6444,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.0", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5ef86ac7927d2de08dc1e26eb91325f9ccbe6309" + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5ef86ac7927d2de08dc1e26eb91325f9ccbe6309", - "reference": "5ef86ac7927d2de08dc1e26eb91325f9ccbe6309", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5dad3780023a707f4c24beac7d57aead85c1ce3c", + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c", "shasum": "" }, "require": { @@ -6422,7 +6497,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.0" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.1" }, "funding": [ { @@ -6438,7 +6513,7 @@ "type": "tidelift" } ], - "time": "2021-11-28T15:25:38+00:00" + "time": "2021-12-09T12:46:57+00:00" }, { "name": "symfony/http-kernel", @@ -7091,16 +7166,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.0", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "89ab66eaef230c9cd1992de2e9a1b26652b127b9" + "reference": "2366ac8d8abe0c077844613c1a4f0c0a9f522dcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/89ab66eaef230c9cd1992de2e9a1b26652b127b9", - "reference": "89ab66eaef230c9cd1992de2e9a1b26652b127b9", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2366ac8d8abe0c077844613c1a4f0c0a9f522dcc", + "reference": "2366ac8d8abe0c077844613c1a4f0c0a9f522dcc", "shasum": "" }, "require": { @@ -7160,7 +7235,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.1" }, "funding": [ { @@ -7176,7 +7251,7 @@ "type": "tidelift" } ], - "time": "2021-11-29T15:30:56+00:00" + "time": "2021-12-01T15:04:08+00:00" }, { "name": "tedivm/jshrink", @@ -8090,16 +8165,16 @@ }, { "name": "beberlei/assert", - "version": "v3.3.1", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372" + "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/5e721d7e937ca3ba2cdec1e1adf195f9e5188372", - "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372", + "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", + "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", "shasum": "" }, "require": { @@ -8151,9 +8226,9 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.1" + "source": "https://github.com/beberlei/assert/tree/v3.3.2" }, - "time": "2021-04-18T20:11:03+00:00" + "time": "2021-12-16T21:41:27+00:00" }, { "name": "behat/gherkin", @@ -8220,16 +8295,16 @@ }, { "name": "codeception/codeception", - "version": "4.1.22", + "version": "4.1.24", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f" + "reference": "1fc3a8f4d6cf7350e1b69afd136708cfffc89e9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", - "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/1fc3a8f4d6cf7350e1b69afd136708cfffc89e9e", + "reference": "1fc3a8f4d6cf7350e1b69afd136708cfffc89e9e", "shasum": "" }, "require": { @@ -8279,7 +8354,10 @@ "psr-4": { "Codeception\\": "src/Codeception", "Codeception\\Extension\\": "ext" - } + }, + "files": [ + "functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8303,7 +8381,7 @@ ], "support": { "issues": "https://github.com/Codeception/Codeception/issues", - "source": "https://github.com/Codeception/Codeception/tree/4.1.22" + "source": "https://github.com/Codeception/Codeception/tree/4.1.24" }, "funding": [ { @@ -8311,7 +8389,7 @@ "type": "open_collective" } ], - "time": "2021-08-06T17:15:34+00:00" + "time": "2021-12-16T12:57:06+00:00" }, { "name": "codeception/lib-asserts", @@ -9117,16 +9195,16 @@ }, { "name": "jms/serializer", - "version": "3.16.0", + "version": "3.17.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "9b0b5208a6edb20b979c7d95bdda53a46098a7d9" + "reference": "6e17603abc16c5b6eed41f51844bc51dda51cf94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/9b0b5208a6edb20b979c7d95bdda53a46098a7d9", - "reference": "9b0b5208a6edb20b979c7d95bdda53a46098a7d9", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/6e17603abc16c5b6eed41f51844bc51dda51cf94", + "reference": "6e17603abc16c5b6eed41f51844bc51dda51cf94", "shasum": "" }, "require": { @@ -9147,7 +9225,7 @@ "ocramius/proxy-manager": "^1.0|^2.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.0.2", - "phpunit/phpunit": "^8.0||^9.0", + "phpunit/phpunit": "^8.5.21||^9.0", "psr/container": "^1.0", "symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0", "symfony/expression-language": "^3.2|^4.0|^5.0|^6.0", @@ -9199,7 +9277,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/3.16.0" + "source": "https://github.com/schmittjoh/serializer/tree/3.17.0" }, "funding": [ { @@ -9207,7 +9285,7 @@ "type": "github" } ], - "time": "2021-11-22T07:03:41+00:00" + "time": "2021-12-14T15:01:05+00:00" }, { "name": "lusitanian/oauth", @@ -9330,16 +9408,16 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "3.7.2", + "version": "3.7.3", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "4488b6290bf6870be20c6823203c14a2e3b37cfa" + "reference": "40dd3ebcfcefc8cff70810b3e654e2f9722fc64f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/4488b6290bf6870be20c6823203c14a2e3b37cfa", - "reference": "4488b6290bf6870be20c6823203c14a2e3b37cfa", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/40dd3ebcfcefc8cff70810b3e654e2f9722fc64f", + "reference": "40dd3ebcfcefc8cff70810b3e654e2f9722fc64f", "shasum": "" }, "require": { @@ -9414,22 +9492,22 @@ ], "support": { "issues": "https://github.com/magento/magento2-functional-testing-framework/issues", - "source": "https://github.com/magento/magento2-functional-testing-framework/tree/3.7.2" + "source": "https://github.com/magento/magento2-functional-testing-framework/tree/3.7.3" }, - "time": "2021-12-07T21:41:48+00:00" + "time": "2021-12-16T20:17:37+00:00" }, { "name": "mustache/mustache", - "version": "v2.13.0", + "version": "v2.14.0", "source": { "type": "git", "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4" + "reference": "4e2724dd40ae9499a55e7db7df82665be0ab7e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e95c5a008c23d3151d59ea72484d4f72049ab7f4", - "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/4e2724dd40ae9499a55e7db7df82665be0ab7e34", + "reference": "4e2724dd40ae9499a55e7db7df82665be0ab7e34", "shasum": "" }, "require": { @@ -9464,9 +9542,9 @@ ], "support": { "issues": "https://github.com/bobthecow/mustache.php/issues", - "source": "https://github.com/bobthecow/mustache.php/tree/master" + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.0" }, - "time": "2019-11-23T21:40:31+00:00" + "time": "2021-12-14T14:42:17+00:00" }, { "name": "myclabs/deep-copy", @@ -10035,16 +10113,16 @@ }, { "name": "phpmd/phpmd", - "version": "2.11.0", + "version": "2.11.1", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "3637949092e6471aeaeca66bc6c016f45e459e24" + "reference": "08b60a2eb7e14c23f46ff8865b510ae08b75d0fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/3637949092e6471aeaeca66bc6c016f45e459e24", - "reference": "3637949092e6471aeaeca66bc6c016f45e459e24", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/08b60a2eb7e14c23f46ff8865b510ae08b75d0fd", + "reference": "08b60a2eb7e14c23f46ff8865b510ae08b75d0fd", "shasum": "" }, "require": { @@ -10106,7 +10184,7 @@ "support": { "irc": "irc://irc.freenode.org/phpmd", "issues": "https://github.com/phpmd/phpmd/issues", - "source": "https://github.com/phpmd/phpmd/tree/2.11.0" + "source": "https://github.com/phpmd/phpmd/tree/2.11.1" }, "funding": [ { @@ -10114,20 +10192,20 @@ "type": "tidelift" } ], - "time": "2021-11-29T14:05:52+00:00" + "time": "2021-12-17T11:25:43+00:00" }, { "name": "phpspec/prophecy", - "version": "1.14.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { @@ -10179,9 +10257,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-09-10T09:02:12+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -10768,16 +10846,16 @@ }, { "name": "rector/rector", - "version": "0.12.5", + "version": "0.12.8", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "e50e40e0fe73b88a46f27e086441e1998ceeca3c" + "reference": "dd40e9e1971b1fdb5fb8d818e3ee691a31ac638b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/e50e40e0fe73b88a46f27e086441e1998ceeca3c", - "reference": "e50e40e0fe73b88a46f27e086441e1998ceeca3c", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/dd40e9e1971b1fdb5fb8d818e3ee691a31ac638b", + "reference": "dd40e9e1971b1fdb5fb8d818e3ee691a31ac638b", "shasum": "" }, "require": { @@ -10816,7 +10894,7 @@ "description": "Prefixed and PHP 7.1 downgraded version of rector/rector", "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.12.5" + "source": "https://github.com/rectorphp/rector/tree/0.12.8" }, "funding": [ { @@ -10824,7 +10902,7 @@ "type": "github" } ], - "time": "2021-11-23T17:38:29+00:00" + "time": "2021-12-13T23:55:00+00:00" }, { "name": "sebastian/cli-parser", @@ -11928,16 +12006,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e" + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f268ca40d54617c6e06757f83f699775c9b3ff2e", - "reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", "shasum": "" }, "require": { @@ -11980,7 +12058,7 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2021-10-11T04:00:11+00:00" + "time": "2021-12-12T21:44:58+00:00" }, { "name": "symfony/dotenv", @@ -12690,7 +12768,7 @@ }, { "package": "magento/zendframework1", - "version": "9999999-dev", + "version": "1.14.6.0-beta2", "alias": "1.14.6", "alias_normalized": "1.14.6.0" } @@ -12698,12 +12776,13 @@ "minimum-stability": "stable", "stability-flags": { "magento/composer": 20, - "magento/zendframework1": 20 + "magento/magento-composer-installer": 10, + "magento/zendframework1": 10 }, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", diff --git a/dev/tests/acceptance/tests/_data/not-a.png b/dev/tests/acceptance/tests/_data/not-a.png new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductWithDisabledProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductWithDisabledProductTest.php new file mode 100644 index 0000000000000..da16dd9ba139a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductWithDisabledProductTest.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Bundle; + +use Exception; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CompareArraysRecursively; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * class BundleProductWithDisabledProductTest + * + * Test Bundle product with disabled product and verify graphQl response + */ +class BundleProductWithDisabledProductTest extends GraphQlAbstract +{ + /** + * @var CompareArraysRecursively + */ + private $compareArraysRecursively; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + } + + /** + * Test Bundle product with disabled product test. + * + * @magentoApiDataFixture Magento/Bundle/_files/bundle_product_with_disabled_product_options.php + * + * @throws Exception + */ + public function testBundleProductWithMultipleOptionsWithDisabledProduct(): void + { + $categorySku = 'c1'; + $query + = <<<QUERY +{ + categoryList(filters: {url_path: {eq: "{$categorySku}"}}) { + children_count + id + url_path + url_key + id + products { + total_count + items { + ... on BundleProduct { + id + categories { + id + name + description + } + dynamic_price + price_range { + minimum_price { + regular_price { + value + currency + } + } + maximum_price { + regular_price { + value + currency + } + } + } + sku + name + short_description { + html + } + description { + html + } + stock_status + __typename + url_key + items { + position + uid + option_id + options { + uid + label + id + price + quantity + product { + ... on VirtualProduct { + sku + stock_status + name + } + price_range { + minimum_price { + regular_price { + value + currency + } + } + maximum_price { + regular_price { + value + currency + } + } + } + } + } + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + $this->assertBundleProduct($response); + } + + /** + * Assert bundle product response. + * + * @param array $response + */ + private function assertBundleProduct(array $response): void + { + $this->assertNotEmpty( + $response['categoryList'][0]['products']['items'], + 'Precondition failed: "items" must not be empty' + ); + $productItems = end($response['categoryList'][0]['products']['items'])['items']; + $this->assertEquals(3, count($productItems[0]['options'])); + $this->assertEquals('virtual1', $productItems[0]['options'][0]['product']['sku']); + $this->assertEquals('virtual2', $productItems[0]['options'][1]['product']['sku']); + $this->assertEquals('virtual3', $productItems[0]['options'][2]['product']['sku']); + $this->assertEquals(3, count($productItems[1]['options'])); + $this->assertEquals('virtual1', $productItems[1]['options'][0]['product']['sku']); + $this->assertEquals('virtual2', $productItems[1]['options'][1]['product']['sku']); + $this->assertEquals('virtual3', $productItems[1]['options'][2]['product']['sku']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/Fixtures/CustomerPlaceOrder.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/Fixtures/CustomerPlaceOrder.php index 0386d414b8682..28021304ce029 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/Fixtures/CustomerPlaceOrder.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/Fixtures/CustomerPlaceOrder.php @@ -64,15 +64,19 @@ public function __construct( * * @param array $customerLogin * @param array $productData + * @param array|null $addressData * @return array */ - public function placeOrderWithBundleProduct(array $customerLogin, array $productData): array - { + public function placeOrderWithBundleProduct( + array $customerLogin, + array $productData, + ?array $addressData = null + ): array { $this->customerLogin = $customerLogin; $this->createCustomerCart(); $this->addBundleProduct($productData); - $this->setBillingAddress(); - $shippingMethod = $this->setShippingAddress(); + $this->setBillingAddress($addressData); + $shippingMethod = $this->setShippingAddress($addressData); $paymentMethod = $this->setShippingMethod($shippingMethod); $this->setPaymentMethod($paymentMethod); return $this->doPlaceOrder(); @@ -198,11 +202,13 @@ private function addBundleProduct(array $productData) /** * Set the billing address on the cart * + * @param array $addressData * @return array */ - private function setBillingAddress(): array + private function setBillingAddress(?array $addressData = null): array { - $setBillingAddress = <<<QUERY + $telephone = $addressData['telephone'] ?? '5123456677'; + $setBillingAddress = <<<QUERY mutation { setBillingAddressOnCart( input: { @@ -215,7 +221,7 @@ private function setBillingAddress(): array street: ["test street 1", "test street 2"] city: "Texas City" postcode: "78717" - telephone: "5123456677" + telephone: "{$telephone}" region: "TX" country_code: "US" } @@ -236,10 +242,12 @@ private function setBillingAddress(): array /** * Set the shipping address on the cart and return an available shipping method * + * @param array|null $addressData * @return array */ - private function setShippingAddress(): array + private function setShippingAddress(?array $addressData): array { + $telephone = $addressData['telephone'] ?? '5123456677'; $setShippingAddress = <<<QUERY mutation { setShippingAddressesOnCart( @@ -256,7 +264,7 @@ private function setShippingAddress(): array region: "AL" postcode: "36013" country_code: "US" - telephone: "3347665522" + telephone: "{$telephone}" } } ] @@ -283,10 +291,10 @@ private function setShippingAddress(): array /** * Set the shipping method on the cart and return an available payment method * - * @param array $shippingMethod + * @param array|null $shippingMethod * @return array */ - private function setShippingMethod(array $shippingMethod): array + private function setShippingMethod(?array $shippingMethod): array { $setShippingMethod = <<<QUERY mutation { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/RetrieveOrdersWithBundleProductByOrderNumberTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/RetrieveOrdersWithBundleProductByOrderNumberTest.php index b4c9bd4962cc2..3d523fd7a8f6d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/RetrieveOrdersWithBundleProductByOrderNumberTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/RetrieveOrdersWithBundleProductByOrderNumberTest.php @@ -117,6 +117,35 @@ public function testGetCustomerOrderBundleProduct() $this->assertEquals($expectedBundleOptions, $bundleOptionsFromResponse); } + /** + * Test customer order with bundle product and no telephone in address + * + * @magentoApiDataFixture Magento/Customer/_files/attribute_telephone_not_required_address.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Bundle/_files/bundle_product_two_dropdown_options.php + */ + public function testOrderBundleProductWithNoTelephoneInAddress() + { + //Place order with bundled product + $qty = 1; + $bundleSku = 'bundle-product-two-dropdown-options'; + /** @var CustomerPlaceOrder $bundleProductOrderFixture */ + $bundleProductOrderFixture = Bootstrap::getObjectManager()->create(CustomerPlaceOrder::class); + $orderResponse = $bundleProductOrderFixture->placeOrderWithBundleProduct( + ['email' => 'customer@example.com', 'password' => 'password'], + ['sku' => $bundleSku, 'quantity' => $qty], + ['telephone' => ''] + ); + $orderNumber = $orderResponse['placeOrder']['order']['order_number']; + $customerOrderResponse = $this->getCustomerOrderQueryBundleProduct($orderNumber); + $customerOrderItems = $customerOrderResponse[0]; + $this->assertEquals("Pending", $customerOrderItems['status']); + $billingAddress = $customerOrderItems['billing_address']; + $shippingAddress = $customerOrderItems['shipping_address']; + $this->assertNull($billingAddress['telephone']); + $this->assertNull($shippingAddress['telephone']); + } + /** * Test customer order details with bundle products * @magentoApiDataFixture Magento/Customer/_files/customer.php @@ -220,59 +249,81 @@ private function getCustomerOrderQueryBundleProduct($orderNumber) $query = <<<QUERY { - customer { - orders(filter:{number:{eq:"{$orderNumber}"}}) { - total_count - items { - id - number - order_date - status - items{ - __typename - product_sku - product_name - product_url_key - product_sale_price{value} - quantity_ordered - discounts{amount{value} label} - ... on BundleOrderItem{ - bundle_options{ - __typename - label - values { - product_sku - product_name - quantity - price { - value - currency - } + customer { + orders(filter:{number:{eq:"{$orderNumber}"}}) { + total_count + items { + id + number + order_date + status + items{ + __typename + product_sku + product_name + product_url_key + product_sale_price{value} + quantity_ordered + discounts{amount{value} label} + ... on BundleOrderItem{ + bundle_options{ + __typename + label + values { + product_sku + product_name + quantity + price { + value + currency + } + } + } + } } - } - } - } - total { - base_grand_total{value currency} - grand_total{value currency} - subtotal {value currency } - total_tax{value currency} - taxes {amount{value currency} title rate} - total_shipping{value currency} - shipping_handling - { - amount_including_tax{value} - amount_excluding_tax{value} - total_amount{value} - discounts{amount{value}} - taxes {amount{value} title rate} - } - discounts {amount{value currency} label} - } - } - } - } - } + total { + base_grand_total{value currency} + grand_total{value currency} + subtotal {value currency } + total_tax{value currency} + taxes {amount{value currency} title rate} + total_shipping{value currency} + shipping_handling + { + amount_including_tax{value} + amount_excluding_tax{value} + total_amount{value} + discounts{amount{value}} + taxes {amount{value} title rate} + } + discounts {amount{value currency} label} + } + billing_address { + firstname + lastname + street + city + region + region_id + postcode + telephone + country_code + } + shipping_address { + firstname + lastname + street + city + region + region_id + postcode + telephone + country_code + } + } + } + } +} QUERY; $currentEmail = 'customer@example.com'; $currentPassword = 'password'; diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php index a981a7b957ec7..df8d79c5fff6d 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php @@ -12,12 +12,18 @@ use Magento\Bundle\Model\Product\Price; use Magento\Bundle\Model\Product\Type as BundleType; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\StoreManagerInterface; @@ -250,6 +256,134 @@ public function stockConfigDataProvider(): array return $variations; } + /** + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Bundle/_files/bundle_product_with_dynamic_price.php + * @dataProvider shouldUpdateBundleStockStatusIfChildProductsStockStatusChangedDataProvider + * @param bool $isOption1Required + * @param bool $isOption2Required + * @param array $outOfStockConfig + * @param array $inStockConfig + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\StateException + */ + public function testShouldUpdateBundleStockStatusIfChildProductsStockStatusChanged( + bool $isOption1Required, + bool $isOption2Required, + array $outOfStockConfig, + array $inStockConfig + ): void { + $sku = 'bundle_product_with_dynamic_price'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + /** @var ProductInterface $product */ + $product = $productRepository->get($sku, true, null, true); + $extension = $product->getExtensionAttributes(); + $options = $extension->getBundleProductOptions(); + $options[0]->setRequired($isOption1Required); + $options[1]->setRequired($isOption2Required); + $extension->setBundleProductOptions($options); + $stockItem = $extension->getStockItem(); + $stockItem->setUseConfigManageStock(1); + $product->setExtensionAttributes($extension); + $productRepository->save($product); + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertTrue($stockItem->getIsInStock()); + foreach ($outOfStockConfig as $childSku => $stockData) { + $this->updateStockItem($childSku, $stockData); + } + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertFalse($stockItem->getIsInStock()); + foreach ($inStockConfig as $childSku => $stockData) { + $this->updateStockItem($childSku, $stockData); + } + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertTrue($stockItem->getIsInStock()); + } + + /** + * @return array + */ + public function shouldUpdateBundleStockStatusIfChildProductsStockStatusChangedDataProvider(): array + { + return [ + 'all options are required' => [ + true, + true, + 'out-of-stock' => [ + 'simple1' => [ + 'is_in_stock' => false + ], + ], + 'in-stock' => [ + 'simple1' => [ + 'is_in_stock' => true + ] + ] + ], + 'all options are optional' => [ + false, + false, + 'out-of-stock' => [ + 'simple1' => [ + 'is_in_stock' => false + ], + 'simple2' => [ + 'is_in_stock' => false + ], + ], + 'in-stock' => [ + 'simple1' => [ + 'is_in_stock' => true + ] + ] + ] + ]; + } + + /** + * @param string $sku + * @param array $data + * @throws NoSuchEntityException + */ + private function updateStockItem(string $sku, array $data): void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($sku, true, null, true); + $extendedAttributes = $product->getExtensionAttributes(); + $stockItem = $extendedAttributes->getStockItem(); + $stockItem->setIsInStock($data['is_in_stock']); + $extendedAttributes->setStockItem($stockItem); + $product->setExtensionAttributes($extendedAttributes); + $productRepository->save($product); + } + + /** + * @param int $productId + * @return StockItemInterface|null + */ + private function getStockItem(int $productId): ?StockItemInterface + { + $criteriaFactory = $this->objectManager->create(StockItemCriteriaInterfaceFactory::class); + $stockItemRepository = $this->objectManager->create(StockItemRepositoryInterface::class); + $stockConfiguration = $this->objectManager->create(StockConfigurationInterface::class); + $criteria = $criteriaFactory->create(); + $criteria->setScopeFilter($stockConfiguration->getDefaultScopeId()); + $criteria->setProductsFilter($productId); + $stockItemCollection = $stockItemRepository->getList($criteria); + $stockItems = $stockItemCollection->getItems(); + return reset($stockItems); + } + /** * @param float $selectionQty * @param float $qty diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options.php new file mode 100644 index 0000000000000..5661c508efbae --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Bundle\Api\Data\LinkInterfaceFactory; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\Catalog\Model\Product\Type; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; +use Magento\Bundle\Api\Data\OptionInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +Resolver::getInstance()->requireDataFixture( + 'Magento/Catalog/_files/multiple_products_with_disabled_virtual_product.php' +); + +$objectManager = Bootstrap::getObjectManager(); + +$productIds = range(101, 104, 1); + +foreach ($productIds as $productId) { + /** @var Item $stockItem */ + $stockItem = $objectManager->create(Item::class); + $stockItem->load($productId, 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($productId); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setIsQtyDecimal(0); + if ($productId == 104) { + $stockItem->setQty(0); + $stockItem->setIsInStock(0); + } else { + $stockItem->setQty(1000); + $stockItem->setIsInStock(1); + } + $stockItem->save(); +} + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->setTypeId(Type::TYPE_BUNDLE) + ->setId(3) + ->setCategoryIds([565]) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setPriceView(1) + ->setPriceType(1) + ->setPrice(10.0) + ->setShipmentType(0) + ->setBundleOptionsData( + [ + // Required "Drop-down" option 1 + [ + 'title' => 'Option 1', + 'default_title' => 'Option 1', + 'type' => 'select', + 'required' => 1, + 'position' => 1, + 'delete' => '', + ], + // Required "Drop-down" option 2 + [ + 'title' => 'Option 2', + 'default_title' => 'Option 2', + 'type' => 'select', + 'required' => 1, + 'position' => 2, + 'delete' => '', + ] + ] + )->setBundleSelectionsData( + [ + [ + [ + 'product_id' => 101, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ], + [ + 'product_id' => 102, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ], + [ + 'product_id' => 103, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ], + [ + 'product_id' => 104, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ] + ], + [ + [ + 'product_id' => 101, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 2 + ], + [ + 'product_id' => 102, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 2 + ], + [ + 'product_id' => 103, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 2 + ] + ] + ] + ); +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +if ($product->getBundleOptionsData()) { + $options = []; + foreach ($product->getBundleOptionsData() as $key => $optionData) { + if (!(bool)$optionData['delete']) { + $option = $objectManager->create(OptionInterfaceFactory::class) + ->create(['data' => $optionData]); + $option->setSku($product->getSku()); + $option->setOptionId(null); + + $links = []; + $bundleLinks = $product->getBundleSelectionsData(); + if (!empty($bundleLinks[$key])) { + foreach ($bundleLinks[$key] as $linkData) { + if (!(bool)$linkData['delete']) { + $link = $objectManager->create(LinkInterfaceFactory::class) + ->create(['data' => $linkData]); + $linkProduct = $productRepository->getById($linkData['product_id']); + $link->setSku($linkProduct->getSku()); + $link->setQty($linkData['selection_qty']); + $links[] = $link; + } + } + $option->setProductLinks($links); + $options[] = $option; + } + } + } + $extension = $product->getExtensionAttributes(); + $extension->setBundleProductOptions($options); + $product->setExtensionAttributes($extension); +} +$product->save(); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options_rollback.php new file mode 100644 index 0000000000000..3d542ebbb0df4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_with_disabled_product_options_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture( + 'Magento/Catalog/_files/multiple_products_with_disabled_virtual_product_rollback.php' +); + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +//delete bundle product +try { + $product = $productRepository->get('bundle-product'); + $productRepository->delete($product); +} catch (NoSuchEntityException $exception) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php index 46a88b1ec8b13..d1d4ccacace7f 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php @@ -9,9 +9,16 @@ Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/multiple_products.php'); $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $productIds = range(10, 12, 1); foreach ($productIds as $productId) { + $product = $productRepository->getById($productId, true, null, true); + if ((int) $product->getStatus() === \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) { + $product->unlockAttribute('status') + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + $product->save(); + } /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ $stockItem = $objectManager->create(\Magento\CatalogInventory\Model\Stock\Item::class); $stockItem->load($productId, 'product_id'); @@ -167,7 +174,6 @@ ] ] ); -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); if ($product->getBundleOptionsData()) { $options = []; diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php index 7228e21f0a026..883b41387a2b0 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php @@ -5,18 +5,26 @@ */ namespace Magento\BundleImportExport\Model\Import\Product\Type; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Filesystem; use Magento\ImportExport\Model\Import; +use Magento\ImportExport\Model\Import\Adapter as ImportAdapter; +use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\Import\Source\Csv; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; /** * @magentoAppArea adminhtml + * @magentoAppIsolation enabled * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class BundleTest extends \Magento\TestFramework\Indexer\TestCase @@ -24,12 +32,12 @@ class BundleTest extends \Magento\TestFramework\Indexer\TestCase /** * Bundle product test Name */ - const TEST_PRODUCT_NAME = 'Bundle 1'; + private const TEST_PRODUCT_NAME = 'Bundle 1'; /** * Bundle product test Type */ - const TEST_PRODUCT_TYPE = 'bundle'; + private const TEST_PRODUCT_TYPE = 'bundle'; /** * @var \Magento\CatalogImportExport\Model\Import\Product @@ -81,29 +89,8 @@ public function testBundleImport() { // import data from CSV file $pathToFile = __DIR__ . '/../../_files/import_bundle.csv'; - $filesystem = $this->objectManager->create( - Filesystem::class - ); - - $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( - Csv::class, - [ - 'file' => $pathToFile, - 'directory' => $directory - ] - ); - $errors = $this->model->setSource( - $source - )->setParameters( - [ - 'behavior' => Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product' - ] - )->validateData(); - - $this->assertTrue($errors->getErrorsCount() == 0); - $this->model->importData(); + $errors = $this->doImport($pathToFile, Import::BEHAVIOR_APPEND); + $this->assertEquals(0, $errors->getErrorsCount()); $resource = $this->objectManager->get(ProductResource::class); $productId = $resource->getIdBySku(self::TEST_PRODUCT_NAME); @@ -161,50 +148,13 @@ public function testBundleImportUpdateValues(array $expectedValues): void { // import data from CSV file $pathToFile = __DIR__ . '/../../_files/import_bundle.csv'; - $filesystem = $this->objectManager->create( - Filesystem::class - ); - - $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( - Csv::class, - [ - 'file' => $pathToFile, - 'directory' => $directory - ] - ); - $errors = $this->model->setSource( - $source - )->setParameters( - [ - 'behavior' => Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product' - ] - )->validateData(); - - $this->assertTrue($errors->getErrorsCount() == 0); - $this->model->importData(); + $errors = $this->doImport($pathToFile, Import::BEHAVIOR_APPEND); + $this->assertEquals(0, $errors->getErrorsCount()); // import data from CSV file to update values $pathToFile2 = __DIR__ . '/../../_files/import_bundle_update_values.csv'; - $source2 = $this->objectManager->create( - Csv::class, - [ - 'file' => $pathToFile2, - 'directory' => $directory - ] - ); - $errors2 = $this->model->setSource( - $source2 - )->setParameters( - [ - 'behavior' => Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product' - ] - )->validateData(); - - $this->assertTrue($errors2->getErrorsCount() == 0); - $this->model->importData(); + $errors = $this->doImport($pathToFile2, Import::BEHAVIOR_APPEND); + $this->assertEquals(0, $errors->getErrorsCount()); $resource = $this->objectManager->get(ProductResource::class); $productId = $resource->getIdBySku(self::TEST_PRODUCT_NAME); @@ -244,24 +194,8 @@ public function testBundleImportWithMultipleStoreViews(): void { // import data from CSV file $pathToFile = __DIR__ . '/../../_files/import_bundle_multiple_store_views.csv'; - $filesystem = $this->objectManager->create(Filesystem::class); - $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( - Csv::class, - [ - 'file' => $pathToFile, - 'directory' => $directory, - ] - ); - $errors = $this->model->setSource($source) - ->setParameters( - [ - 'behavior' => Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product', - ] - )->validateData(); - $this->assertTrue($errors->getErrorsCount() == 0); - $this->model->importData(); + $errors = $this->doImport($pathToFile, Import::BEHAVIOR_APPEND); + $this->assertEquals(0, $errors->getErrorsCount()); $resource = $this->objectManager->get(ProductResource::class); $productId = $resource->getIdBySku(self::TEST_PRODUCT_NAME); $this->assertIsNumeric($productId); @@ -323,6 +257,121 @@ public function valuesDataProvider(): array ]; } + /** + * @magentoDbIsolation enabled + * @dataProvider shouldUpdateBundleStockStatusIfChildProductsStockStatusChangedDataProvider + * @param bool $isOption1Required + * @param bool $isOption2Required + * @param string $outOfStockImportFile + * @param string $inStockImportFile + * @throws NoSuchEntityException + */ + public function testShouldUpdateBundleStockStatusIfChildProductsStockStatusChanged( + bool $isOption1Required, + bool $isOption2Required, + string $outOfStockImportFile, + string $inStockImportFile + ): void { + // import data from CSV file + $pathToFile = __DIR__ . '/../../_files/import_bundle.csv'; + $errors = $this->doImport($pathToFile, Import::BEHAVIOR_APPEND); + $this->assertEquals(0, $errors->getErrorsCount()); + $this->importedProductSkus = ['Simple 1', 'Simple 2', 'Simple 3', 'Bundle 1']; + $sku = 'Bundle 1'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + /** @var ProductInterface $product */ + $product = $productRepository->get($sku, true, null, true); + $options = $product->getExtensionAttributes()->getBundleProductOptions(); + $options[0]->setRequired($isOption1Required); + $options[1]->setRequired($isOption2Required); + $extension = $product->getExtensionAttributes(); + $extension->setBundleProductOptions($options); + $product->setExtensionAttributes($extension); + $productRepository->save($product); + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertTrue($stockItem->getIsInStock()); + + $errors = $this->doImport(__DIR__ . '/../../_files/' . $outOfStockImportFile); + $this->assertEquals(0, $errors->getErrorsCount()); + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertFalse($stockItem->getIsInStock()); + + $errors = $this->doImport(__DIR__ . '/../../_files/' . $inStockImportFile); + $this->assertEquals(0, $errors->getErrorsCount()); + + $stockItem = $this->getStockItem((int) $product->getId()); + $this->assertNotNull($stockItem); + $this->assertTrue($stockItem->getIsInStock()); + } + + /** + * @return array + */ + public function shouldUpdateBundleStockStatusIfChildProductsStockStatusChangedDataProvider(): array + { + return [ + 'all options are required' => [ + true, + true, + 'out-of-stock' => 'import_bundle_set_option1_products_out_of_stock.csv', + 'in-stock' => 'import_bundle_set_option1_products_in_stock.csv' + ], + 'all options are optional' => [ + false, + false, + 'out-of-stock' => 'import_bundle_set_all_products_out_of_stock.csv', + 'in-stock' => 'import_bundle_set_option1_products_in_stock.csv' + ] + ]; + } + + /** + * @param int $productId + * @return StockItemInterface|null + */ + private function getStockItem(int $productId): ?StockItemInterface + { + $criteriaFactory = $this->objectManager->create(StockItemCriteriaInterfaceFactory::class); + $stockItemRepository = $this->objectManager->create(StockItemRepositoryInterface::class); + $stockConfiguration = $this->objectManager->create(StockConfigurationInterface::class); + $criteria = $criteriaFactory->create(); + $criteria->setScopeFilter($stockConfiguration->getDefaultScopeId()); + $criteria->setProductsFilter($productId); + $stockItemCollection = $stockItemRepository->getList($criteria); + $stockItems = $stockItemCollection->getItems(); + return reset($stockItems); + } + + /** + * @param string $file + * @param string $behavior + * @param bool $validateOnly + * @return ProcessingErrorAggregatorInterface + */ + private function doImport( + string $file, + string $behavior = Import::BEHAVIOR_ADD_UPDATE, + bool $validateOnly = false + ): ProcessingErrorAggregatorInterface { + /** @var Filesystem $filesystem */ + $filesystem =$this->objectManager->create(Filesystem::class); + $directoryWrite = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = ImportAdapter::findAdapterFor($file, $directoryWrite); + $errors = $this->model + ->setParameters(['behavior' => $behavior, 'entity' => 'catalog_product']) + ->setSource($source) + ->validateData(); + if (!$validateOnly && !$errors->getAllErrors()) { + $this->model->importData(); + } + return $errors; + } + /** * teardown */ diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_all_products_out_of_stock.csv b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_all_products_out_of_stock.csv new file mode 100644 index 0000000000000..36e8e87196ac1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_all_products_out_of_stock.csv @@ -0,0 +1,4 @@ +"sku","qty","is_in_stock" +"Simple 1","0","0" +"Simple 2","0","0" +"Simple 3","0","0" diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_in_stock.csv b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_in_stock.csv new file mode 100644 index 0000000000000..ac888ecbc7079 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_in_stock.csv @@ -0,0 +1,2 @@ +"sku","qty","is_in_stock" +"Simple 1","100","1" diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_out_of_stock.csv b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_out_of_stock.csv new file mode 100644 index 0000000000000..64830cdf3619c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_set_option1_products_out_of_stock.csv @@ -0,0 +1,2 @@ +"sku","qty","is_in_stock" +"Simple 1","0","0" diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php index 523a1abd2e645..7e11e3d05473f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php @@ -164,6 +164,7 @@ public function testGotErrorDuringCreateAttributeSetWithoutName(): void [ 'gotoEdit' => '1', 'skeleton_set' => $this->getCatalogProductDefaultAttributeSetId(), + 'attribute_set_name' => '' ] ); $this->dispatch('backend/catalog/product_set/save/'); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product.php new file mode 100644 index 0000000000000..5cb63985311df --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\TestFramework\Helper\Bootstrap; + +//test category +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId('565') + ->setName('c1') + ->setAttributeSetId('3') + ->setParentId(2) + ->setPath('1/2/565') + ->setLevel('2') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->save(); + +//virtual product 1 +/** @var $product Product */ +$product = Bootstrap::getObjectManager()->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Product\Type::TYPE_VIRTUAL) + ->setId(101) + ->setAttributeSetId(4) + ->setName('Virtual Product1') + ->setSku('virtual1') + ->setTaxClassId('none') + ->setDescription('description unique word') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setPrice(10) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([565]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->save(); + +//virtual product 2 +$product = Bootstrap::getObjectManager()->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Product\Type::TYPE_VIRTUAL) + ->setId(102) + ->setAttributeSetId(4) + ->setName('Virtual Product2') + ->setSku('virtual2') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setPrice(20) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_IN_CATALOG) + ->setStatus(Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([565]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->save(); + +//virtual product 3 +$product = Bootstrap::getObjectManager()->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Product\Type::TYPE_VIRTUAL) + ->setId(103) + ->setAttributeSetId(4) + ->setName('Virtual Product3') + ->setSku('virtual3') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setPrice(30) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_IN_CATALOG) + ->setStatus(Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([565]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->save(); + +//virtual product 4 +$product = Bootstrap::getObjectManager()->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Product\Type::TYPE_VIRTUAL) + ->setId(104) + ->setAttributeSetId(4) + ->setName('Virtual Product4') + ->setSku('virtual4') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setPrice(40) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_IN_CATALOG) + ->setStatus(Status::STATUS_DISABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([565]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 0, 'is_qty_decimal' => 0, 'is_in_stock' => 0]) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product_rollback.php new file mode 100644 index 0000000000000..0a7ee384e5428 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_disabled_virtual_product_rollback.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +//delete category +/** @var CategoryCollection $collection */ +$categoryCollection = $objectManager->create(CategoryCollection::class); +$categoryCollection + ->addAttributeToFilter('level', 2) + ->load() + ->delete(); + +//delete product +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +foreach (['virtual1', 'virtual2', 'virtual3', 'virtual4'] as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (NoSuchEntityException $exception) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php index f5568ced2c96a..7e6c1ba5100f2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php @@ -6,11 +6,14 @@ declare(strict_types=1); use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Model\Indexer\Stock\Processor; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; +use Magento\Framework\Registry; use Magento\TestFramework\Helper\Bootstrap; -$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry = Bootstrap::getObjectManager()->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); @@ -26,5 +29,7 @@ } catch (StateException $exception) { } +ObjectManager::getInstance()->create(Processor::class)->reindexAll(); + $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php index 0bef038b5f7e5..b06de8109ecbd 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php @@ -247,7 +247,8 @@ private function conditionProvider() 'simple-product-9', 'simple-product-10', 'simple-product-11', - 'simple-product-12' + 'simple-product-12', + 'simple-product-13', ] ], @@ -272,7 +273,8 @@ private function conditionProvider() 'simple-product-9', 'simple-product-10', 'simple-product-11', - 'simple-product-12' + 'simple-product-12', + 'simple-product-13', ] ], @@ -386,7 +388,8 @@ private function conditionProvider() 'simple-product-9', 'simple-product-10', 'simple-product-11', - 'simple-product-12' + 'simple-product-12', + 'simple-product-13', ] ], @@ -416,7 +419,8 @@ private function conditionProvider() 'simple-product-9', 'simple-product-10', 'simple-product-11', - 'simple-product-12' + 'simple-product-12', + 'simple-product-13', ] ], @@ -424,12 +428,9 @@ private function conditionProvider() 'variation 22' => [ 'condition' => $this->getConditionsForVariation22(), 'expected-sku' => [ - 'simple-product-1', - 'simple-product-2', - 'simple-product-3', - 'simple-product-4', 'simple-product-7', - 'simple-product-8' + 'simple-product-8', + 'simple-product-13', ] ], ]; diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/conditions_to_collection/products.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/conditions_to_collection/products.php index db44a3d10ac9c..71c3d2369506e 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/conditions_to_collection/products.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/conditions_to_collection/products.php @@ -173,6 +173,19 @@ 'qty' => 42, 'categories' => ['Category 1.1.1'], ], + [ + 'type-id' => 'simple', + 'attribute-set-id' => $attributeSetGuardians->getId(), + 'website-ids' => [1], + 'name' => 'Simple Product 13', + 'sku' => 'simple-product-13', + 'price' => 10, + 'visibility' => \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH, + 'status' => \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, + 'stock-data' => ['use_config_manage_stock' => 1, 'qty' => 22, 'is_in_stock' => 1], + 'qty' => 42, + 'categories' => [], + ], ]; foreach ($productsData as $productData) { diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Indexer/Product/ProductRuleIndexerTest.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Indexer/Product/ProductRuleIndexerTest.php index 7463b0967833f..d040cad330aff 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Indexer/Product/ProductRuleIndexerTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Indexer/Product/ProductRuleIndexerTest.php @@ -49,51 +49,109 @@ protected function setUp(): void } /** + * @dataProvider productsDataProvider + * @param string $reindexSku + * @param array $expectedPrices * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function testExecute(): void + public function testExecute(string $reindexSku, array $expectedPrices): void { - $product = $this->productRepository->get('configurable'); + $product = $this->productRepository->get($reindexSku); $this->productRuleIndexer->execute([$product->getId()]); - $product = $this->productRepository->get('simple_10'); - $price = $this->getCatalogRulePrice($product); - $this->assertEquals(5, $price); + $this->assertEquals($expectedPrices, $this->getCatalogRulePrices(array_keys($expectedPrices))); } /** + * @dataProvider productsDataProvider + * @param string $reindexSku + * @param array $expectedPrices * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function testExecuteRow(): void + public function testExecuteRow(string $reindexSku, array $expectedPrices): void { - $product = $this->productRepository->get('configurable'); + $product = $this->productRepository->get($reindexSku); $this->productRuleIndexer->executeRow($product->getId()); - $product = $this->productRepository->get('simple_10'); - $price = $this->getCatalogRulePrice($product); - $this->assertEquals(5, $price); + $this->assertEquals($expectedPrices, $this->getCatalogRulePrices(array_keys($expectedPrices))); } /** + * @dataProvider productsDataProvider + * @param string $reindexSku + * @param array $expectedPrices * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function testExecuteList(): void + public function testExecuteList(string $reindexSku, array $expectedPrices): void { - $product = $this->productRepository->get('configurable'); + $product = $this->productRepository->get($reindexSku); $this->productRuleIndexer->executeList([$product->getId()]); - $product = $this->productRepository->get('simple_10'); - $price = $this->getCatalogRulePrice($product); - $this->assertEquals(5, $price); + $this->assertEquals($expectedPrices, $this->getCatalogRulePrices(array_keys($expectedPrices))); } + /** + * @return void + */ public function testExecuteFull(): void { $this->productRuleIndexer->executeFull(); - $product = $this->productRepository->get('simple_10'); - $price = $this->getCatalogRulePrice($product); - $this->assertEquals(5, $price); + $expectedPrices = [ + 'simple_10' => 5, + 'simple_20' => 10, + ]; + $this->assertEquals($expectedPrices, $this->getCatalogRulePrices(array_keys($expectedPrices))); + } + + /** + * @return array + */ + public function productsDataProvider(): array + { + return [ + [ + 'configurable', + [ + 'simple_10' => 5, + 'simple_20' => 10, + ] + ], + [ + 'simple_10', + [ + 'simple_10' => 5, + 'simple_20' => 10, + ] + ], + [ + 'simple_20', + [ + 'simple_10' => 5, + 'simple_20' => 10, + ] + ], + ]; + } + + /** + * @param array $skus + * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getCatalogRulePrices(array $skus): array + { + $actualPrices = []; + foreach ($skus as $sku) { + $product = $this->productRepository->get($sku); + $actualPrices[$sku] = $this->getCatalogRulePrice($product); + } + return $actualPrices; } /** diff --git a/dev/tests/integration/testsuite/Magento/Cron/Model/Config/Backend/SitemapTest.php b/dev/tests/integration/testsuite/Magento/Cron/Model/Config/Backend/SitemapTest.php new file mode 100644 index 0000000000000..6e40366eee04d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cron/Model/Config/Backend/SitemapTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cron\Model\Config\Backend; + +use Magento\Config\Model\Config as ConfigModel; +use Magento\Config\Model\PreparedValueFactory; +use Magento\Cron\Model\Config\Source\Frequency; +use Magento\Framework\App\Config\ValueFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * @magentoAppArea adminhtml + */ +class SitemapTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + } + + /** + * @dataProvider frequencyDataProvider + * @param string $frequency + * @param string $expectedCronExpr + */ + public function testDirectSave(string $frequency, string $expectedCronExpr): void + { + $preparedValueFactory = $this->objectManager->get(PreparedValueFactory::class); + /** @var Sitemap $sitemapValue */ + $sitemapValue = $preparedValueFactory->create('sitemap/generate/frequency', $frequency, 'default', 0); + $sitemapValue->save(); + + self::assertEquals($expectedCronExpr, $this->getCronExpression()); + } + + /** + * @dataProvider frequencyDataProvider + * @param string $frequency + * @param string $expectedCronExpr + */ + public function testSaveFromAdmin(string $frequency, string $expectedCronExpr): void + { + $config = $this->objectManager->create(ConfigModel::class); + $config->setSection('sitemap'); + $config->setGroups( + [ + 'generate' => [ + 'fields' => [ + 'time' => ['value' => ['00', '00', '00']], + 'frequency' => ['value' => $frequency], + ], + ], + ] + ); + $config->save(); + + self::assertEquals($expectedCronExpr, $this->getCronExpression()); + } + + /** + * @return array + */ + public function frequencyDataProvider(): array + { + return [ + 'daily' => [Frequency::CRON_DAILY, '0 0 * * *'], + 'weekly' => [Frequency::CRON_WEEKLY, '0 0 * * 1'], + 'monthly' => [Frequency::CRON_MONTHLY, '0 0 1 * *'], + ]; + } + + /** + * @return string + */ + private function getCronExpression(): string + { + $valueFactory = $this->objectManager->get(ValueFactory::class); + $cronExprValue = $valueFactory->create() + ->load(Sitemap::CRON_STRING_PATH, 'path'); + + return $cronExprValue->getValue(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/Address/DataProviderTest.php b/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/Address/DataProviderTest.php index 12e7b1bd3d0e9..f6a3328973b53 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/Address/DataProviderTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/Address/DataProviderTest.php @@ -42,7 +42,6 @@ class DataProviderTest extends TestCase */ protected function setUp(): void { - $this->initLocaleResolverMock(); $this->requestMock = $this->createMock(RequestInterface::class); $this->dataProvider = Bootstrap::getObjectManager()->create( DataProvider::class, @@ -68,7 +67,9 @@ public function testGetDataByRegion(array $filterData) { $customerId = 1; $locale = 'JA_jp'; + $this->initLocaleResolverMock(); $this->localeResolverMock->method('getLocale')->willReturn($locale); + $this->requestMock->method('getParam')->with('parent_id')->willReturn($customerId); $this->dataProvider = Bootstrap::getObjectManager()->create( DataProvider::class, @@ -108,9 +109,16 @@ public function getDataByRegionDataProvider(): array private function initLocaleResolverMock() { $this->localeResolverMock = $this->createMock(ResolverInterface::class); - Bootstrap::getObjectManager()->removeSharedInstance(ResolverInterface::class); - Bootstrap::getObjectManager()->removeSharedInstance(Resolver::class); Bootstrap::getObjectManager()->addSharedInstance($this->localeResolverMock, ResolverInterface::class); Bootstrap::getObjectManager()->addSharedInstance($this->localeResolverMock, Resolver::class); } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + Bootstrap::getObjectManager()->removeSharedInstance(ResolverInterface::class); + Bootstrap::getObjectManager()->removeSharedInstance(Resolver::class); + } } diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/Model/File/ContentUploaderTest.php b/dev/tests/integration/testsuite/Magento/Downloadable/Model/File/ContentUploaderTest.php new file mode 100644 index 0000000000000..6a330b9f5c242 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/Model/File/ContentUploaderTest.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Downloadable\Model\File; + +use Magento\Downloadable\Api\Data\File\ContentInterface; +use Magento\Downloadable\Api\Data\File\ContentInterfaceFactory; +use Magento\Downloadable\Model\Link; +use Magento\Downloadable\Model\Sample; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\MediaStorage\Helper\File\Storage; +use Magento\MediaStorage\Helper\File\Storage\Database; +use Magento\MediaStorage\Model\File\Validator\NotProtectedExtension; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Integration test for \Magento\Downloadable\Model\File\ContentUploader class + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ContentUploaderTest extends TestCase +{ + /** + * @var ContentInterface + */ + private $fileContent; + + /** + * @var string + */ + private $filePath; + + /** + * @var ContentUploader + */ + private $model; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->fileContent = $this->objectManager->create(ContentInterfaceFactory::class)->create(); + $fixtureDir = realpath(__DIR__ . '/../../_files'); + $this->filePath = $fixtureDir . DIRECTORY_SEPARATOR . 'test_image.jpg'; + $this->fileContent->setFileData(base64_encode(file_get_contents($this->filePath))); + $this->fileContent->setName('test_image.jpg'); + + $database = $this->getMockBuilder(Database::class) + ->disableOriginalConstructor() + ->getMock(); + $storage = $this->getMockBuilder(Storage::class) + ->disableOriginalConstructor() + ->getMock(); + $validator = $this->getMockBuilder(NotProtectedExtension::class) + ->disableOriginalConstructor() + ->getMock(); + $filesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->onlyMethods(['getDirectoryWrite']) + ->getMock(); + $systemTmpDirectory = $this->getMockForAbstractClass( + WriteInterface::class, + [], + '', + false, + true, + true, + ['writeFile'] + ); + $systemTmpDirectory->expects($this->once())->method('writeFile')->willReturn(1); + $systemTmpDirectory->method('getAbsolutePath')->willReturn($this->filePath); + + $filesystem->method('getDirectoryWrite')->willReturn($systemTmpDirectory); + $link = $this->getMockBuilder(Link::class) + ->disableOriginalConstructor() + ->getMock(); + $sampleConfig = $this->getMockBuilder(Sample::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = $this->getMockForAbstractClass( + ContentUploader::class, + [$database, $storage, $validator, $filesystem, $link, $sampleConfig], + '', + true, + true, + true, + ['save'] + ); + } + + public function testUploadWithSuccessSave() + { + $data = ['path' => $this->filePath, 'file' => 'test_image.jpg']; + $this->model->expects($this->once())->method('save')->willReturn($data); + $result = $this->model->upload($this->fileContent, 'sample'); + $this->assertIsArray($result); + $this->assertArrayHasKey('status', $result); + $this->assertArrayHasKey('name', $result); + } + + public function testUploadWithFalseSave() + { + $this->model->expects($this->once())->method('save')->willReturn(false); + $this->assertFalse($this->model->upload($this->fileContent, 'sample')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Catalog/Model/Indexer/Category/Product/Action/RowsTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Catalog/Model/Indexer/Category/Product/Action/RowsTest.php index 8ba07e3448848..7d3e65a44f98e 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Catalog/Model/Indexer/Category/Product/Action/RowsTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Catalog/Model/Indexer/Category/Product/Action/RowsTest.php @@ -54,6 +54,14 @@ class RowsTest extends \PHPUnit\Framework\TestCase */ private $fulltextSearchCollectionFactory; + /** + * Elasticsearch7 engine configuration is also compatible with OpenSearch 1 + */ + private const ENGINE_SUPPORTED_VERSIONS = [ + 7 => 'elasticsearch7', + 1 => 'elasticsearch7', + ]; + /** * @inheritdoc */ @@ -71,7 +79,14 @@ protected function setUp(): void protected function assertPreConditions(): void { $currentEngine = $this->objectManager->get(EngineResolverInterface::class)->getCurrentSearchEngine(); - $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + $this->assertEquals( + $this->getInstalledSearchEngine(), + $currentEngine, + sprintf( + 'Search engine configuration "%s" is not compatible with the installed version', + $currentEngine + ) + ); } /** @@ -84,7 +99,7 @@ private function getInstalledSearchEngine(): string if (!$this->searchEngine) { // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." $version = $this->objectManager->get(ElasticsearchVersionChecker::class)->getVersion(); - $this->searchEngine = 'elasticsearch' . $version; + $this->searchEngine = self::ENGINE_SUPPORTED_VERSIONS[$version] ?? 'elasticsearch' . $version; } return $this->searchEngine; diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php index 6e47854cf7f9a..edaeec59190b2 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php @@ -58,6 +58,14 @@ class ElasticsearchTest extends \PHPUnit\Framework\TestCase */ private $productRepository; + /** + * Elasticsearch7 engine configuration is also compatible with OpenSearch 1 + */ + private const ENGINE_SUPPORTED_VERSIONS = [ + 7 => 'elasticsearch7', + 1 => 'elasticsearch7', + ]; + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); @@ -78,7 +86,14 @@ protected function setUp(): void protected function assertPreConditions(): void { $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); - $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + $this->assertEquals( + $this->getInstalledSearchEngine(), + $currentEngine, + sprintf( + 'Search engine configuration "%s" is not compatible with the installed version', + $currentEngine + ) + ); } /** @@ -169,7 +184,7 @@ private function getInstalledSearchEngine() if (!$this->searchEngine) { // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); - $this->searchEngine = 'elasticsearch' . $version; + $this->searchEngine = self::ENGINE_SUPPORTED_VERSIONS[$version] ?? 'elasticsearch' . $version; } return $this->searchEngine; } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php index 0173a643dd7bd..e9e7a704fc4a2 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php @@ -72,6 +72,14 @@ class IndexHandlerTest extends TestCase */ private $searchIndexNameResolver; + /** + * Elasticsearch7 engine configuration is also compatible with OpenSearch 1 + */ + private const ENGINE_SUPPORTED_VERSIONS = [ + 7 => 'elasticsearch7', + 1 => 'elasticsearch7', + ]; + /** * {@inheritdoc} */ @@ -100,7 +108,14 @@ protected function setUp(): void protected function assertPreConditions(): void { $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); - $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + $this->assertEquals( + $this->getInstalledSearchEngine(), + $currentEngine, + sprintf( + 'Search engine configuration "%s" is not compatible with the installed version', + $currentEngine + ) + ); } /** @@ -306,7 +321,7 @@ private function getInstalledSearchEngine() if (!$this->searchEngine) { // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); - $this->searchEngine = 'elasticsearch' . $version; + $this->searchEngine = self::ENGINE_SUPPORTED_VERSIONS[$version] ?? 'elasticsearch' . $version; } return $this->searchEngine; } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php index 643632e699429..44d40b9992ab8 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php @@ -63,6 +63,14 @@ class ReindexAllTest extends \PHPUnit\Framework\TestCase */ private $productRepository; + /** + * Elasticsearch7 engine configuration is also compatible with OpenSearch 1 + */ + private const ENGINE_SUPPORTED_VERSIONS = [ + 7 => 'elasticsearch7', + 1 => 'elasticsearch7', + ]; + protected function setUp(): void { $this->connectionManager = Bootstrap::getObjectManager()->create(ConnectionManager::class); @@ -79,7 +87,14 @@ protected function setUp(): void protected function assertPreConditions(): void { $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); - $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + $this->assertEquals( + $this->getInstalledSearchEngine(), + $currentEngine, + sprintf( + 'Search engine configuration "%s" is not compatible with the installed version', + $currentEngine + ) + ); } /** @@ -279,7 +294,7 @@ private function getInstalledSearchEngine() if (!$this->searchEngine) { // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); - $this->searchEngine = 'elasticsearch' . $version; + $this->searchEngine = self::ENGINE_SUPPORTED_VERSIONS[$version] ?? 'elasticsearch' . $version; } return $this->searchEngine; } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php index 73411d9ce4a17..589351620b443 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php @@ -50,6 +50,14 @@ class AdapterTest extends \PHPUnit\Framework\TestCase */ private $searchEngine; + /** + * Elasticsearch7 engine configuration is also compatible with OpenSearch 1 + */ + private const ENGINE_SUPPORTED_VERSIONS = [ + 7 => 'elasticsearch7', + 1 => 'elasticsearch7', + ]; + /** * @inheritdoc */ @@ -104,7 +112,14 @@ protected function createAdapter() protected function assertPreConditions(): void { $currentEngine = $this->objectManager->get(EngineResolverInterface::class)->getCurrentSearchEngine(); - $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + $this->assertEquals( + $this->getInstalledSearchEngine(), + $currentEngine, + sprintf( + 'Search engine configuration "%s" is not compatible with the installed version', + $currentEngine + ) + ); } /** @@ -737,7 +752,7 @@ private function getInstalledSearchEngine() if (!$this->searchEngine) { // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." $version = $this->objectManager->get(ElasticsearchVersionChecker::class)->getVersion(); - $this->searchEngine = 'elasticsearch' . $version; + $this->searchEngine = self::ENGINE_SUPPORTED_VERSIONS[$version] ?? 'elasticsearch' . $version; } return $this->searchEngine; } diff --git a/dev/tests/integration/testsuite/Magento/Framework/HTTP/AsyncClientInterfaceTest.php b/dev/tests/integration/testsuite/Magento/Framework/HTTP/AsyncClientInterfaceTest.php index 8e4da60f06bfe..d300ff7ea6716 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/HTTP/AsyncClientInterfaceTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/HTTP/AsyncClientInterfaceTest.php @@ -35,13 +35,19 @@ protected function setUp(): void */ public function testRequest(): void { - $request = new Request('https://magento.com', Request::METHOD_GET, [], null); + $request = new Request('https://adobe.com', Request::METHOD_GET, [], null); $response1 = $this->client->request($request); $response2 = $this->client->request($request); $this->assertEquals(200, $response2->get()->getStatusCode()); $this->assertEquals(200, $response1->get()->getStatusCode()); - $this->assertStringContainsString('Magento Commerce', $response1->get()->getBody()); - $this->assertStringContainsString('Magento Commerce', $response2->get()->getBody()); + $this->assertStringContainsString( + 'Adobe: Creative, marketing and document management solutions', + $response1->get()->getBody() + ); + $this->assertStringContainsString( + 'Adobe: Creative, marketing and document management solutions', + $response2->get()->getBody() + ); $date1 = new \DateTime($response1->get()->getHeaders()['date']); $date2 = new \DateTime($response2->get()->getHeaders()['date']); $this->assertLessThanOrEqual(1, abs((int)$date1->format('U') - (int)$date2->format('U'))); @@ -56,7 +62,7 @@ public function testCancel(): void $this->expectException(\Magento\Framework\Async\CancelingDeferredException::class); $this->expectExceptionMessage('Deferred is canceled'); - $request = new Request('https://magento.com/home-page', Request::METHOD_GET, [], null); + $request = new Request('https://adobe.com/', Request::METHOD_GET, [], null); $response = $this->client->request($request); $response->cancel(true); $this->assertTrue($response->isCancelled()); @@ -72,7 +78,7 @@ public function testCancelFail(): void $this->expectException(\Magento\Framework\Async\CancelingDeferredException::class); $this->expectExceptionMessage('Failed to cancel HTTP request'); - $request = new Request('https://magento.com/home-page', Request::METHOD_GET, [], null); + $request = new Request('https://adobe.com/', Request::METHOD_GET, [], null); $response = $this->client->request($request); $response->cancel(); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Element/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Element/TemplateTest.php index cb6cfd05ed1d3..1ca01eeccec74 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/Element/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Element/TemplateTest.php @@ -116,6 +116,7 @@ public function testGetObjectData() public function testGetCacheKeyInfo() { + $this->_block->setTemplate('non-existing-template.phtml'); $this->assertArrayHasKey('template', $this->_block->getCacheKeyInfo()); } } diff --git a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php index 52e7191a97226..698080fa79676 100644 --- a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php @@ -55,12 +55,12 @@ class SynchronizeFilesTest extends TestCase */ protected function setUp(): void { - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); $this->getAssetKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); + $this->driver = $this->mediaDirectory->getDriver(); } /** @@ -82,9 +82,9 @@ public function testExecute( ): void { $path = realpath(__DIR__ . '/../_files/' . $file); $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); - $this->driver->copy( - $path, - $modifiableFilePath + $this->driver->filePutContents( + $modifiableFilePath, + file_get_contents($path) ); $this->synchronizeFiles->execute([$file]); @@ -95,7 +95,6 @@ public function testExecute( $this->assertEquals($title, $loadedAssets->getTitle()); $this->assertEquals($description, $loadedAssets->getDescription()); $this->assertEquals($keywords, $loadedKeywords); - $this->driver->deleteFile($modifiableFilePath); } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php index 94cf6ef108474..7bda40c440628 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php @@ -116,8 +116,8 @@ protected function tearDown(): void } /** - * @magentoDataFixture Magento/Catalog/_files/product_virtual.php * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/Catalog/_files/product_virtual.php * @return void */ public function testCollectTotalsWithVirtual(): void diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/Report/Rule/CreatedatTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/Report/Rule/CreatedatTest.php index 393f9f1cc925b..6f50ce57309e5 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/Report/Rule/CreatedatTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/Report/Rule/CreatedatTest.php @@ -58,7 +58,8 @@ public function testTotals($orderParams) private function getTotalAmount(\Magento\Sales\Model\Order $order) { return ( - $order->getBaseSubtotal() - $order->getBaseSubtotalCanceled() + ($order->getBaseSubtotal() - $order->getBaseSubtotalCanceled() + + ($order->getBaseShippingAmount() - $order->getBaseShippingCanceled())) - (abs((float) $order->getBaseDiscountAmount()) - abs((float) $order->getBaseDiscountCanceled())) + ($order->getBaseTaxAmount() - $order->getBaseTaxCanceled()) ) * $order->getBaseToGlobalRate(); @@ -73,7 +74,8 @@ private function getTotalAmount(\Magento\Sales\Model\Order $order) private function getTotalAmountActual(\Magento\Sales\Model\Order $order) { return ( - $order->getBaseSubtotalInvoiced() - $order->getSubtotalRefunded() + ($order->getBaseSubtotalInvoiced() - $order->getSubtotalRefunded() + + ($order->getBaseShippingInvoiced() - $order->getBaseShippingRefunded())) - abs((float) $order->getBaseDiscountInvoiced()) - abs((float) $order->getBaseDiscountRefunded()) + $order->getBaseTaxInvoiced() - $order->getBaseTaxRefunded() ) * $order->getBaseToGlobalRate(); diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php index ebda955569e52..886142bf3ed65 100644 --- a/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php +++ b/dev/tests/integration/testsuite/Magento/Shipping/Block/Adminhtml/Order/AddToPackageTest.php @@ -75,13 +75,13 @@ public function testGetCommentsHtml(): void * * @magentoDataFixture Magento/Shipping/_files/shipping_with_carrier_data_different_currency_code.php */ - public function testGetCurrencyCodeCustomValue () + public function testGetCurrencyCodeCustomValue() { $template = '/<span class="customs-value-currency">\s*?(?<currency>[A-Za-z]+)\s*?<\/span>/'; $matches = []; preg_match($template, $this->getHtml(), $matches); $currency = $matches['currency'] ?? null; - $this->assertEquals('FR',$currency ); + $this->assertEquals('FR', $currency); } /** @@ -102,6 +102,7 @@ private function getHtml() $this->registry->register('current_shipment', $shipment); $block->setTemplate('Magento_Shipping::order/packaging/popup.phtml'); + $block->setNameInLayout('test'); return $block->toHtml(); } diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/_files/sitemap_products.php b/dev/tests/integration/testsuite/Magento/Sitemap/_files/sitemap_products.php index 895b418a4e495..22c513aa5d436 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/_files/sitemap_products.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/_files/sitemap_products.php @@ -15,12 +15,16 @@ $filesystem = $objectManager->get(\Magento\Framework\Filesystem::class); /** @var \Magento\Framework\Filesystem\Directory\WriteInterface $mediaDirectory */ $mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); -$mediaPath = $mediaDirectory->getAbsolutePath(); $baseTmpMediaPath = $config->getBaseTmpMediaPath(); $mediaDirectory->create($baseTmpMediaPath); -copy(__DIR__ . '/magento_image_sitemap.png', $mediaPath . '/' . $baseTmpMediaPath . '/magento_image_sitemap.png'); -copy(__DIR__ . '/second_image.png', $mediaPath . '/' . $baseTmpMediaPath . '/second_image.png'); +$imageSitemapPath = $mediaDirectory->getAbsolutePath($baseTmpMediaPath . '/magento_image_sitemap.png'); +$secondImagePath = $mediaDirectory->getAbsolutePath($baseTmpMediaPath . '/second_image.png'); + +$imageSitemapContent = file_get_contents(__DIR__ . '/magento_image_sitemap.png'); +$secondImageContent = file_get_contents(__DIR__ . '/second_image.png'); +$mediaDirectory->getDriver()->filePutContents($imageSitemapPath, $imageSitemapContent); +$mediaDirectory->getDriver()->filePutContents($secondImagePath, $secondImageContent); $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->setTypeId( @@ -140,12 +144,12 @@ )->setThumbnail( '/m/a/magento_image_sitemap.png' )->addImageToMediaGallery( - $mediaPath . '/' . $baseTmpMediaPath . '/magento_image_sitemap.png', + $imageSitemapPath, null, false, false )->addImageToMediaGallery( - $mediaPath . '/' . $baseTmpMediaPath . '/second_image.png', + $secondImagePath, null, false, false diff --git a/dev/tests/integration/testsuite/Magento/Test/Integrity/Magento/Payment/MethodsTest.php b/dev/tests/integration/testsuite/Magento/Test/Integrity/Magento/Payment/MethodsTest.php index a308365880d0c..eb5c66cc9c777 100644 --- a/dev/tests/integration/testsuite/Magento/Test/Integrity/Magento/Payment/MethodsTest.php +++ b/dev/tests/integration/testsuite/Magento/Test/Integrity/Magento/Payment/MethodsTest.php @@ -26,7 +26,7 @@ class MethodsTest extends \PHPUnit\Framework\TestCase */ public function testPaymentMethod($code, $methodClass) { - if ($code == 'vault') { + if (in_array($code, ['free', 'substitution', 'vault', 'payflowpro_cc_vault', 'fake_vault'])) { return; } Bootstrap::getObjectManager()->configure($this->getTestConfiguration()); @@ -71,7 +71,6 @@ public function testPaymentMethod($code, $methodClass) /** @var $block \Magento\Framework\View\Element\Template */ $block = $blockFactory->createBlock($blockClass); $block->setArea('frontend'); - $this->assertFileExists((string)$block->getTemplateFile(), $message); if ($model->canUseInternal()) { try { Bootstrap::getObjectManager()->get( diff --git a/dev/tests/integration/testsuite/Magento/Theme/Block/Html/FooterTest.php b/dev/tests/integration/testsuite/Magento/Theme/Block/Html/FooterTest.php index 8640a40de07c3..5032e56c382e0 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Block/Html/FooterTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Block/Html/FooterTest.php @@ -30,10 +30,20 @@ public function testGetCacheKeyInfo() $context = $objectManager->get(\Magento\Framework\App\Http\Context::class); $context->setValue(Context::CONTEXT_AUTH, false, false); $block = $objectManager->get(\Magento\Framework\View\LayoutInterface::class) - ->createBlock(\Magento\Theme\Block\Html\Footer::class); + ->createBlock(\Magento\Theme\Block\Html\Footer::class) + ->setTemplate('html/copyright.phtml'); $storeId = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore()->getId(); + $expected = [ + 'PAGE_FOOTER', + $storeId, + 0, + $this->_theme->getId(), + false, + $block->getTemplateFile(), + 'template' => 'html/copyright.phtml' + ]; $this->assertEquals( - ['PAGE_FOOTER', $storeId, 0, $this->_theme->getId(), false, $block->getTemplateFile(), 'template' => null], + $expected, $block->getCacheKeyInfo() ); } diff --git a/dev/tests/integration/testsuite/Magento/User/Block/User/Edit/Tab/MainTest.php b/dev/tests/integration/testsuite/Magento/User/Block/User/Edit/Tab/MainTest.php index bcdb1169c44c4..fa7b0c202bbe0 100644 --- a/dev/tests/integration/testsuite/Magento/User/Block/User/Edit/Tab/MainTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Block/User/Edit/Tab/MainTest.php @@ -28,6 +28,7 @@ protected function setUp(): void $this->_block = $objectManager->create(\Magento\User\Block\User\Edit\Tab\Main::class); $this->_block->setArea('adminhtml'); + $this->_block->setNameInLayout('test'); $this->_user = $objectManager->create(\Magento\User\Model\User::class); $objectManager->get(\Magento\Framework\Registry::class)->register('permissions_user', $this->_user); diff --git a/dev/tests/setup-integration/etc/install-config-mysql.php.dist b/dev/tests/setup-integration/etc/install-config-mysql.php.dist index 21511695fa092..5dea43f9fa583 100644 --- a/dev/tests/setup-integration/etc/install-config-mysql.php.dist +++ b/dev/tests/setup-integration/etc/install-config-mysql.php.dist @@ -10,6 +10,7 @@ return [ 'db-user' => '{{db-user}}', 'db-password' => '{{db-password}}', 'db-name' => '{{db-name}}', + 'db-init-statements' => '{{db-init-statements}}', 'db-prefix' => '', 'backend-frontname' => 'admin', 'admin-user' => 'admin', diff --git a/dev/tests/setup-integration/framework/bootstrap.php b/dev/tests/setup-integration/framework/bootstrap.php index e3eed312a21b9..15afc0700e002 100644 --- a/dev/tests/setup-integration/framework/bootstrap.php +++ b/dev/tests/setup-integration/framework/bootstrap.php @@ -83,7 +83,10 @@ $application->createInstallDir(); //We do not want to install anything $application->initialize([]); - $application->cleanup(); + + if ($settings->getAsBoolean('TESTS_CLEANUP')) { + $application->cleanup(); + } \Magento\TestFramework\Helper\Bootstrap::setInstance(new \Magento\TestFramework\Helper\Bootstrap($bootstrap)); diff --git a/dev/tests/unit/phpunit.xml.dist b/dev/tests/unit/phpunit.xml.dist index 2621ddfecb798..4422793f16f33 100644 --- a/dev/tests/unit/phpunit.xml.dist +++ b/dev/tests/unit/phpunit.xml.dist @@ -31,7 +31,7 @@ <exclude>../../../app/code/Magento/Indexer/Test/Unit</exclude> </testsuite> <testsuite name="Magento_Unit_Tests_App_Code_Indexer"> - <directory>../../../app/code/Magento/Indexer/Test/Unit</directory> + <directory>../../../app/code/*/Indexer/Test/Unit</directory> </testsuite> <testsuite name="Magento_Unit_Tests_Other"> <directory>../../../lib/internal/*/*/Test/Unit</directory> diff --git a/lib/internal/Magento/Framework/Amqp/composer.json b/lib/internal/Magento/Framework/Amqp/composer.json index 3639aa4be2e2c..e6789f63d205a 100644 --- a/lib/internal/Magento/Framework/Amqp/composer.json +++ b/lib/internal/Magento/Framework/Amqp/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "php-amqplib/php-amqplib": "~3.1.0" }, "autoload": { diff --git a/lib/internal/Magento/Framework/Api/Search/Document.php b/lib/internal/Magento/Framework/Api/Search/Document.php index c1edec9860f67..555b1525013ce 100644 --- a/lib/internal/Magento/Framework/Api/Search/Document.php +++ b/lib/internal/Magento/Framework/Api/Search/Document.php @@ -14,7 +14,7 @@ class Document extends AbstractSimpleObject implements DocumentInterface, \IteratorAggregate { /** - * {@inheritdoc} + * @inheritdoc */ public function getId() { @@ -22,7 +22,7 @@ public function getId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setId($id) { @@ -30,7 +30,7 @@ public function setId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomAttribute($attributeCode) { @@ -38,7 +38,7 @@ public function getCustomAttribute($attributeCode) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomAttribute($attributeCode, $attributeValue) { @@ -49,7 +49,7 @@ public function setCustomAttribute($attributeCode, $attributeValue) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomAttributes() { @@ -57,7 +57,7 @@ public function getCustomAttributes() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomAttributes(array $attributes) { @@ -70,6 +70,7 @@ public function setCustomAttributes(array $attributes) * @return \ArrayIterator * @since 100.1.0 */ + #[\ReturnTypeWillChange] public function getIterator() { $attributes = (array)$this->getCustomAttributes(); diff --git a/lib/internal/Magento/Framework/App/Cache/Frontend/Pool.php b/lib/internal/Magento/Framework/App/Cache/Frontend/Pool.php index daa7cba20139d..7ad55576df19e 100644 --- a/lib/internal/Magento/Framework/App/Cache/Frontend/Pool.php +++ b/lib/internal/Magento/Framework/App/Cache/Frontend/Pool.php @@ -17,7 +17,7 @@ class Pool implements \Iterator /** * Frontend identifier associated with the default settings */ - const DEFAULT_FRONTEND_ID = 'default'; + public const DEFAULT_FRONTEND_ID = 'default'; /** * @var DeploymentConfig @@ -97,6 +97,7 @@ protected function _getCacheSettings() * * @return \Magento\Framework\Cache\FrontendInterface */ + #[\ReturnTypeWillChange] public function current() { $this->_initialize(); @@ -106,6 +107,7 @@ public function current() /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { $this->_initialize(); @@ -115,6 +117,7 @@ public function key() /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { $this->_initialize(); @@ -124,6 +127,7 @@ public function next() /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { $this->_initialize(); @@ -133,6 +137,7 @@ public function rewind() /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { $this->_initialize(); diff --git a/lib/internal/Magento/Framework/App/RouterList.php b/lib/internal/Magento/Framework/App/RouterList.php index 75ea8766c960f..0a172bf47e25a 100644 --- a/lib/internal/Magento/Framework/App/RouterList.php +++ b/lib/internal/Magento/Framework/App/RouterList.php @@ -1,13 +1,13 @@ <?php /** - * Router list - * Used as a container for list of routers - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\App; +/** + * Used as a container for list of routers. + */ class RouterList implements RouterListInterface { /** @@ -55,56 +55,49 @@ protected function getRouterInstance($routerId) } /** - * (PHP 5 >= 5.0.0)<br/> - * Return the current element - * @link http://php.net/manual/en/iterator.current.php + * @inheritDoc + * * @return RouterInterface */ + #[\ReturnTypeWillChange] public function current() { return $this->getRouterInstance($this->key()); } /** - * (PHP 5 >= 5.0.0)<br/> - * Move forward to next element - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->routerList); } /** - * (PHP 5 >= 5.0.0)<br/> - * Return the key of the current element - * @link http://php.net/manual/en/iterator.key.php + * @inheritDoc + * * @return string|int|null */ + #[\ReturnTypeWillChange] public function key() { return key($this->routerList); } /** - * (PHP 5 >= 5.0.0)<br/> - * Checks if current position is valid - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean The return value will be casted to boolean and then evaluated. - * Returns true on success or false on failure. + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return !!current($this->routerList); } /** - * (PHP 5 >= 5.0.0)<br/> - * Rewind the Iterator to the first element - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->routerList); diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index b0480510e7f68..00c1e8967c5e1 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -24,26 +24,26 @@ */ class Files { - const INCLUDE_APP_CODE = 1; + public const INCLUDE_APP_CODE = 1; - const INCLUDE_TESTS = 2; + public const INCLUDE_TESTS = 2; - const INCLUDE_DEV_TOOLS = 4; + public const INCLUDE_DEV_TOOLS = 4; - const INCLUDE_TEMPLATES = 8; + public const INCLUDE_TEMPLATES = 8; - const INCLUDE_LIBS = 16; + public const INCLUDE_LIBS = 16; - const INCLUDE_PUB_CODE = 32; + public const INCLUDE_PUB_CODE = 32; - const INCLUDE_NON_CLASSES = 64; + public const INCLUDE_NON_CLASSES = 64; - const INCLUDE_SETUP = 128; + public const INCLUDE_SETUP = 128; /** * Return as data set */ - const AS_DATA_SET = 1024; + public const AS_DATA_SET = 1024; /** * @var ComponentRegistrar @@ -1661,9 +1661,8 @@ public function isModuleExists($moduleName) { $key = __METHOD__ . "/{$moduleName}"; if (!isset(self::$_cache[$key])) { - self::$_cache[$key] = file_exists( - $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName) - ); + $componentPath = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName); + self::$_cache[$key] = $componentPath && file_exists($componentPath); } return self::$_cache[$key]; diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php index 9602807a8aedb..36eb0c9e5a96d 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php @@ -24,6 +24,7 @@ class File extends \SplFileObject * * @return string */ + #[\ReturnTypeWillChange] public function current() { return $this->_currentStatement; @@ -34,6 +35,7 @@ public function current() * * @return void */ + #[\ReturnTypeWillChange] public function next() { $this->_currentStatement = ''; @@ -53,6 +55,7 @@ public function next() * * @return void */ + #[\ReturnTypeWillChange] public function rewind() { parent::rewind(); diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php index ae7805e716a73..0cf1834ca1247 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php @@ -39,6 +39,7 @@ public function __construct(Iterator $iterator, array $filters) * * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $current = str_replace('\\', '/', $this->current()->__toString()); diff --git a/lib/internal/Magento/Framework/Bulk/composer.json b/lib/internal/Magento/Framework/Bulk/composer.json index 07e9f366dd470..ccd28b80c3f2e 100644 --- a/lib/internal/Magento/Framework/Bulk/composer.json +++ b/lib/internal/Magento/Framework/Bulk/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "autoload": { "psr-4": { diff --git a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php index ade1542d5fbd5..93f159a56d4e9 100644 --- a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php @@ -14,7 +14,7 @@ class ArgumentsReader { use GetParameterClassTrait; - const NO_DEFAULT_VALUE = 'NO-DEFAULT'; + public const NO_DEFAULT_VALUE = 'NO-DEFAULT'; /** * @var NamespaceResolver @@ -110,7 +110,11 @@ private function processType(\ReflectionClass $class, \Laminas\Code\Reflection\P $type = $parameter->detectType(); - if ($type === 'null') { + /** + * $type === null if it is unspecified + * $type === 'null' if it is used in doc block + */ + if ($type === null || $type === 'null') { return null; } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php index f867b7e337862..9bf8ae3721738 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php @@ -46,34 +46,62 @@ public function testGetConstructorArgumentsClassWithAllArgumentsType() 'isOptional' => false, 'default' => null, ], + 'noType' => [ + 'name' => 'noType', + 'position' => 3, + 'type' => '\\\\noType', + 'isOptional' => false, + 'default' => null, + ], 'const' => [ 'name' => 'const', - 'position' => 3, + 'position' => 4, 'type' => 'string', 'isOptional' => true, 'default' => 'Const Value', ], 'optionalNumValue' => [ 'name' => 'optionalNumValue', - 'position' => 4, + 'position' => 5, 'type' => 'int', 'isOptional' => true, 'default' => 9807, ], 'optionalStringValue' => [ 'name' => 'optionalStringValue', - 'position' => 5, + 'position' => 6, 'type' => 'string', 'isOptional' => true, 'default' => 'optional string', ], 'optionalArrayValue' => [ 'name' => 'optionalArrayValue', - 'position' => 6, + 'position' => 7, 'type' => 'array', 'isOptional' => true, 'default' => "array('optionalKey' => 'optionalValue')", ], + 'optNullValue' => [ + 'name' => 'optNullValue', + 'position' => 8, + 'type' => null, + 'isOptional' => true, + 'default' => null, + ], + 'optNullIntValue' => [ + 'name' => 'optNullIntValue', + 'position' => 9, + 'type' => null, + 'isOptional' => true, + 'default' => 1, + ], + 'optNoTypeValue' => [ + 'name' => 'optNoTypeValue', + 'position' => 10, + 'type' => '\\\\optNoTypeValue', + 'isOptional' => true, + 'default' => null, + ], ]; $class = new \ReflectionClass('ClassWithAllArgumentTypes'); $actualResult = $this->_model->getConstructorArguments($class); @@ -113,34 +141,62 @@ public function testGetConstructorArgumentsClassWithoutOwnConstructorInheritedTr 'isOptional' => false, 'default' => null, ], + 'noType' => [ + 'name' => 'noType', + 'position' => 3, + 'type' => '\\\\noType', + 'isOptional' => false, + 'default' => null, + ], 'const' => [ 'name' => 'const', - 'position' => 3, + 'position' => 4, 'type' => 'string', 'isOptional' => true, 'default' => 'Const Value', ], 'optionalNumValue' => [ 'name' => 'optionalNumValue', - 'position' => 4, + 'position' => 5, 'type' => 'int', 'isOptional' => true, 'default' => 9807, ], 'optionalStringValue' => [ 'name' => 'optionalStringValue', - 'position' => 5, + 'position' => 6, 'type' => 'string', 'isOptional' => true, 'default' => 'optional string', ], 'optionalArrayValue' => [ 'name' => 'optionalArrayValue', - 'position' => 6, + 'position' => 7, 'type' => 'array', 'isOptional' => true, 'default' => "array('optionalKey' => 'optionalValue')", ], + 'optNullValue' => [ + 'name' => 'optNullValue', + 'position' => 8, + 'type' => null, + 'isOptional' => true, + 'default' => null, + ], + 'optNullIntValue' => [ + 'name' => 'optNullIntValue', + 'position' => 9, + 'type' => null, + 'isOptional' => true, + 'default' => 1, + ], + 'optNoTypeValue' => [ + 'name' => 'optNoTypeValue', + 'position' => 10, + 'type' => '\\\\optNoTypeValue', + 'isOptional' => true, + 'default' => null, + ], ]; $class = new \ReflectionClass('ClassWithoutOwnConstruct'); $actualResult = $this->_model->getConstructorArguments($class, false, true); diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php index 800cd2851eb66..dcc5a7c11e553 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php @@ -47,23 +47,52 @@ class ClassWithAllArgumentTypes */ protected $_constValue; + /** + * Test property without specified type + */ + private $noType; + + /** + * @var null + */ + private $optNullValue; + + /** + * @var int|null + */ + private ?int $optNullIntValue; + + /** + * @var null + */ + private $optNoTypeValue; + /** * @param stdClass $stdClassObject * @param ClassWithoutConstruct $withoutConstructorClassObject * @param mixed $someVariable + * @param $noType * @param string $const * @param int $optionalNumValue * @param string $optionalStringValue * @param array $optionalArrayValue + * @param null $optNullValue + * @param null|int $optNullIntValue first type from defined will be used + * @param $optNoTypeValue + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \stdClass $stdClassObject, \ClassWithoutConstruct $withoutConstructorClassObject, $someVariable, + $noType, $const = \ClassWithAllArgumentTypes::DEFAULT_VALUE, $optionalNumValue = 9807, $optionalStringValue = 'optional string', - $optionalArrayValue = ['optionalKey' => 'optionalValue'] + $optionalArrayValue = ['optionalKey' => 'optionalValue'], + $optNullValue = null, + $optNullIntValue = 1, + $optNoTypeValue = null ) { $this->_stdClassObject = $stdClassObject; $this->_withoutConstructorClassObject = $withoutConstructorClassObject; @@ -72,6 +101,10 @@ public function __construct( $this->_optionalStringValue = $optionalStringValue; $this->_optionalArrayValue = $optionalArrayValue; $this->_constValue = $const; + $this->noType = $noType; + $this->optNullValue = $optNullValue; + $this->optNullIntValue = $optNullIntValue; + $this->optNoTypeValue = $optNoTypeValue; } } class ClassWithoutOwnConstruct extends ClassWithAllArgumentTypes diff --git a/lib/internal/Magento/Framework/Config/FileIterator.php b/lib/internal/Magento/Framework/Config/FileIterator.php index 7cf1f34b6deb6..83fc5fa95d7bf 100644 --- a/lib/internal/Magento/Framework/Config/FileIterator.php +++ b/lib/internal/Magento/Framework/Config/FileIterator.php @@ -10,29 +10,22 @@ use Magento\Framework\Filesystem\File\ReadFactory; /** - * Class FileIterator * @api * @since 100.0.2 */ class FileIterator implements \Iterator, \Countable { /** - * Paths - * * @var array */ protected $paths = []; /** - * Position - * * @var int */ protected $position; /** - * File read factory - * * @var ReadFactory */ protected $fileReadFactory; @@ -55,6 +48,7 @@ public function __construct(ReadFactory $readFactory, array $paths) * * @return void */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->paths); @@ -65,6 +59,7 @@ public function rewind() * * @return string */ + #[\ReturnTypeWillChange] public function current() { $fileRead = $this->fileReadFactory->create($this->key(), DriverPool::FILE); @@ -76,6 +71,7 @@ public function current() * * @return mixed */ + #[\ReturnTypeWillChange] public function key() { return current($this->paths); @@ -86,6 +82,7 @@ public function key() * * @return void */ + #[\ReturnTypeWillChange] public function next() { next($this->paths); @@ -96,6 +93,7 @@ public function next() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return (bool) $this->key(); @@ -106,6 +104,7 @@ public function valid() * * @return array */ + #[\ReturnTypeWillChange] public function toArray() { $result = []; @@ -120,6 +119,7 @@ public function toArray() * * @return int */ + #[\ReturnTypeWillChange] public function count() { return count($this->paths); diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 1f9d4190ae795..7004297432808 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -1750,7 +1750,7 @@ public function getColumnCreateByDescribe($columnData) $options['unsigned'] = true; } if ($columnData['NULLABLE'] === false - && !($type == Table::TYPE_TEXT && strlen($columnData['DEFAULT']) != 0) + && !($type == Table::TYPE_TEXT && isset($columnData['DEFAULT']) && strlen($columnData['DEFAULT']) != 0) ) { $options['nullable'] = false; } diff --git a/lib/internal/Magento/Framework/DB/Query/BatchIteratorInterface.php b/lib/internal/Magento/Framework/DB/Query/BatchIteratorInterface.php index b1ce64e610fb5..c52268e363c0d 100644 --- a/lib/internal/Magento/Framework/DB/Query/BatchIteratorInterface.php +++ b/lib/internal/Magento/Framework/DB/Query/BatchIteratorInterface.php @@ -6,7 +6,7 @@ namespace Magento\Framework\DB\Query; /** - * Batch Iterator interface + * The batch queries iterator interface */ interface BatchIteratorInterface extends \Iterator { @@ -14,13 +14,13 @@ interface BatchIteratorInterface extends \Iterator * Constant which determine strategy to create iterator which will to process * range field eg. entity_id with unique values. */ - const UNIQUE_FIELD_ITERATOR = "unique"; + public const UNIQUE_FIELD_ITERATOR = "unique"; /** * Constant which determine strategy to create iterator which will to process * range field with non-unique values. */ - const NON_UNIQUE_FIELD_ITERATOR = "non_unqiue"; + public const NON_UNIQUE_FIELD_ITERATOR = "non_unqiue"; /** * Return the current element @@ -29,6 +29,7 @@ interface BatchIteratorInterface extends \Iterator * * @return \Magento\Framework\DB\Select */ + #[\ReturnTypeWillChange] public function current(); /** @@ -38,6 +39,7 @@ public function current(); * * @return int */ + #[\ReturnTypeWillChange] public function key(); /** @@ -48,6 +50,7 @@ public function key(); * * @return \Magento\Framework\DB\Select */ + #[\ReturnTypeWillChange] public function next(); /** @@ -57,6 +60,7 @@ public function next(); * * @return void */ + #[\ReturnTypeWillChange] public function rewind(); /** @@ -64,5 +68,6 @@ public function rewind(); * * @return bool */ + #[\ReturnTypeWillChange] public function valid(); } diff --git a/lib/internal/Magento/Framework/DB/Sql/Expression.php b/lib/internal/Magento/Framework/DB/Sql/Expression.php index 514033187a8e6..4a373a22d6dbe 100644 --- a/lib/internal/Magento/Framework/DB/Sql/Expression.php +++ b/lib/internal/Magento/Framework/DB/Sql/Expression.php @@ -13,6 +13,7 @@ class Expression extends \Zend_Db_Expr implements ExpressionInterface, \JsonSeri /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/lib/internal/Magento/Framework/DB/Tree/NodeSet.php b/lib/internal/Magento/Framework/DB/Tree/NodeSet.php index e861a90f7cd0d..3cbbaef8284dd 100644 --- a/lib/internal/Magento/Framework/DB/Tree/NodeSet.php +++ b/lib/internal/Magento/Framework/DB/Tree/NodeSet.php @@ -46,6 +46,8 @@ public function __construct() } /** + * Adds a node to node list. + * * @param Node $node * @return int * @@ -59,30 +61,39 @@ public function addNode(Node $node) } /** + * Retrieves count elements in node list. + * * @return int * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function count() { return $this->count; } /** + * Checks if current position is valid. + * * @return bool * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function valid() { return isset($this->_nodes[$this->_current]); } /** + * Move forward to next element. + * * @return false|int * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function next() { if ($this->_current > $this->_currentNode) { @@ -93,30 +104,39 @@ public function next() } /** + * Retrieves the key of the current element. + * * @return int * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function key() { return $this->_current; } /** + * Retrieves the current node. + * * @return Node * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function current() { return $this->_nodes[$this->_current]; } /** + * Rewinds the Iterator to the first element. + * * @return void * * @deprecated 102.0.0 */ + #[\ReturnTypeWillChange] public function rewind() { $this->_current = 0; diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Collection.php b/lib/internal/Magento/Framework/Data/Form/Element/Collection.php index ced1f4208223d..e1b45feb99a3d 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Collection.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Collection.php @@ -45,6 +45,7 @@ public function __construct(AbstractForm $container) * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->_elements); @@ -57,6 +58,7 @@ public function getIterator() * @param mixed $value * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->_elements[$key] = $value; @@ -68,6 +70,7 @@ public function offsetSet($key, $value) * @param mixed $key * @return AbstractElement */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->_elements[$key]; @@ -79,6 +82,7 @@ public function offsetGet($key) * @param mixed $key * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { unset($this->_elements[$key]); @@ -90,6 +94,7 @@ public function offsetUnset($key) * @param mixed $key * @return boolean */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return isset($this->_elements[$key]); @@ -121,6 +126,7 @@ public function add(AbstractElement $element, $after = false) if ($currElement->getId() == $after) { $newOrderElements[] = $currElement; $newOrderElements[] = $element; + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $this->_elements = array_merge($newOrderElements, array_slice($this->_elements, $index + 1)); return $element; } @@ -167,6 +173,7 @@ public function remove($elementId) * * @return int */ + #[\ReturnTypeWillChange] public function count() { return count($this->_elements); diff --git a/lib/internal/Magento/Framework/Data/SearchResultIterator.php b/lib/internal/Magento/Framework/Data/SearchResultIterator.php index 62d5b88f0d5c7..965a35d339627 100644 --- a/lib/internal/Magento/Framework/Data/SearchResultIterator.php +++ b/lib/internal/Magento/Framework/Data/SearchResultIterator.php @@ -7,9 +7,6 @@ use Magento\Framework\DB\QueryInterface; -/** - * Class SearchResultIterator - */ class SearchResultIterator implements \Iterator { /** @@ -43,16 +40,18 @@ public function __construct(AbstractSearchResult $searchResult, QueryInterface $ } /** - * @return array|mixed + * @inheritdoc */ + #[\ReturnTypeWillChange] public function current() { return $this->current; } /** - * @return void + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { ++$this->key; @@ -60,24 +59,27 @@ public function next() } /** - * @return int|mixed + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { return $this->key; } /** - * @return bool + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return !empty($this->current); } /** - * @return void + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { $this->current = null; diff --git a/lib/internal/Magento/Framework/Data/Tree/Node/Collection.php b/lib/internal/Magento/Framework/Data/Tree/Node/Collection.php index cf6529988eb44..e35a2ac5d991e 100644 --- a/lib/internal/Magento/Framework/Data/Tree/Node/Collection.php +++ b/lib/internal/Magento/Framework/Data/Tree/Node/Collection.php @@ -3,18 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -/** - * Tree node collection - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\Data\Tree\Node; use Magento\Framework\Data\Tree; use Magento\Framework\Data\Tree\Node; /** + * Tree node collection + * * @api * @since 100.0.2 */ @@ -54,6 +50,7 @@ public function getNodes() * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->_nodes); @@ -66,6 +63,7 @@ public function getIterator() * @param mixed $value * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->_nodes[$key] = $value; @@ -73,9 +71,11 @@ public function offsetSet($key, $value) /** * Implementation of \ArrayAccess:offsetGet() + * * @param string $key * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->_nodes[$key]; @@ -83,9 +83,11 @@ public function offsetGet($key) /** * Implementation of \ArrayAccess:offsetUnset() + * * @param string $key * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { unset($this->_nodes[$key]); @@ -93,9 +95,11 @@ public function offsetUnset($key) /** * Implementation of \ArrayAccess:offsetExists() + * * @param string $key * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return isset($this->_nodes[$key]); @@ -103,6 +107,7 @@ public function offsetExists($key) /** * Adds a node to this node + * * @param Node $node * @return Node */ @@ -139,6 +144,7 @@ public function delete($node) * * @return int */ + #[\ReturnTypeWillChange] public function count() { return count($this->_nodes); diff --git a/lib/internal/Magento/Framework/DataObject.php b/lib/internal/Magento/Framework/DataObject.php index 0e7e96808d67f..d74b477d41fa8 100644 --- a/lib/internal/Magento/Framework/DataObject.php +++ b/lib/internal/Magento/Framework/DataObject.php @@ -368,7 +368,8 @@ public function toString($format = '') } else { preg_match_all('/\{\{([a-z0-9_]+)\}\}/is', $format, $matches); foreach ($matches[1] as $var) { - $format = str_replace('{{' . $var . '}}', $this->getData($var), $format); + $data = $this->getData($var) ?? ''; + $format = str_replace('{{' . $var . '}}', $data, $format); } $result = $format; } @@ -503,6 +504,7 @@ public function debug($data = null, &$objects = []) * @return void * @link http://www.php.net/manual/en/arrayaccess.offsetset.php */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->_data[$offset] = $value; @@ -515,6 +517,7 @@ public function offsetSet($offset, $value) * @return bool * @link http://www.php.net/manual/en/arrayaccess.offsetexists.php */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->_data[$offset]) || array_key_exists($offset, $this->_data); @@ -527,6 +530,7 @@ public function offsetExists($offset) * @return void * @link http://www.php.net/manual/en/arrayaccess.offsetunset.php */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->_data[$offset]); @@ -539,6 +543,7 @@ public function offsetUnset($offset) * @return mixed * @link http://www.php.net/manual/en/arrayaccess.offsetget.php */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (isset($this->_data[$offset])) { diff --git a/lib/internal/Magento/Framework/Filesystem/Filter/ExcludeFilter.php b/lib/internal/Magento/Framework/Filesystem/Filter/ExcludeFilter.php index ca099f0cbf608..14421651896dd 100644 --- a/lib/internal/Magento/Framework/Filesystem/Filter/ExcludeFilter.php +++ b/lib/internal/Magento/Framework/Filesystem/Filter/ExcludeFilter.php @@ -35,6 +35,7 @@ public function __construct(\Iterator $iterator, array $filters) * * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $current = str_replace('\\', '/', $this->current()->__toString()); diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/IOLimit/IOLimitConfigProvider.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/IOLimit/IOLimitConfigProvider.php new file mode 100644 index 0000000000000..b5f415bb6fb40 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/IOLimit/IOLimitConfigProvider.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\IOLimit; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Provides configuration related to the GraphQL input limit validation + */ +class IOLimitConfigProvider +{ + /** + * Path to the configuration setting for if the input limiting is enabled + */ + public const CONFIG_PATH_INPUT_LIMIT_ENABLED = 'graphql/validation/input_limit_enabled'; + + /** + * Path to the configuration setting for maximum page size allowed + */ + public const CONFIG_PATH_MAXIMUM_PAGE_SIZE = 'graphql/validation/maximum_page_size'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * Get the stored configuration for if the input limiting is enabled + */ + public function isInputLimitingEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_INPUT_LIMIT_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get the stored configuration for the maximum page size + */ + public function getMaximumPageSize(): ?int + { + $value = $this->scopeConfig->getValue( + self::CONFIG_PATH_MAXIMUM_PAGE_SIZE, + ScopeInterface::SCOPE_STORE + ); + return isset($value) ? (int)$value : null; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/SearchCriteriaValidator.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/SearchCriteriaValidator.php index 9a7f513833f02..b5d2423238c55 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/SearchCriteriaValidator.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/Validator/SearchCriteriaValidator.php @@ -8,8 +8,10 @@ namespace Magento\Framework\GraphQl\Query\Resolver\Argument\Validator; +use Magento\Framework\App\ObjectManager; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface; /** @@ -22,12 +24,20 @@ class SearchCriteriaValidator implements ValidatorInterface */ private $maxPageSize; + /** + * @var IOLimitConfigProvider|null + */ + private $configProvider; + /** * @param int $maxPageSize + * @param IOLimitConfigProvider|null $configProvider */ - public function __construct(int $maxPageSize) + public function __construct(int $maxPageSize, ?IOLimitConfigProvider $configProvider = null) { $this->maxPageSize = $maxPageSize; + $this->configProvider = $configProvider ?? ObjectManager::getInstance() + ->get(IOLimitConfigProvider::class); } /** @@ -35,9 +45,15 @@ public function __construct(int $maxPageSize) */ public function validate(Field $field, $args): void { - if (isset($args['pageSize']) && $args['pageSize'] > $this->maxPageSize) { + if (!$this->configProvider->isInputLimitingEnabled()) { + return; + } + + $max = $this->configProvider->getMaximumPageSize() ?? $this->maxPageSize; + + if (isset($args['pageSize']) && $args['pageSize'] > $max) { throw new GraphQlInputException( - __("Maximum pageSize is %max", ['max' => $this->maxPageSize]) + __("Maximum pageSize is %max", ['max' => $max]) ); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Test/Unit/Query/Resolver/Argument/Validator/SearchCriteriaValidatorTest.php b/lib/internal/Magento/Framework/GraphQl/Test/Unit/Query/Resolver/Argument/Validator/SearchCriteriaValidatorTest.php index d9aa18831aa9e..e48e46c8446dc 100644 --- a/lib/internal/Magento/Framework/GraphQl/Test/Unit/Query/Resolver/Argument/Validator/SearchCriteriaValidatorTest.php +++ b/lib/internal/Magento/Framework/GraphQl/Test/Unit/Query/Resolver/Argument/Validator/SearchCriteriaValidatorTest.php @@ -10,7 +10,9 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\SearchCriteriaValidator; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -18,26 +20,92 @@ */ class SearchCriteriaValidatorTest extends TestCase { + /** + * @var IOLimitConfigProvider|MockObject + */ + private $configProvider; + + /** + * @var SearchCriteriaValidator + */ + private $validator; + + protected function setUp(): void + { + $this->configProvider = self::getMockBuilder(IOLimitConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator = new SearchCriteriaValidator(3, $this->configProvider); + } + + /** + * @doesNotPerformAssertions + */ + public function testValidValueWithDisabledDefaultConfig() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(false); + $field = self::getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator->validate($field, ['pageSize' => 50]); + } + /** * @doesNotPerformAssertions */ public function testValidValue() { - $validator = new SearchCriteriaValidator(3); + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(false); $field = self::getMockBuilder(Field::class) ->disableOriginalConstructor() ->getMock(); - $validator->validate($field, ['pageSize' => 3]); + $this->validator->validate($field, ['pageSize' => 3]); } - public function testValidInvalidMaxValue() + /** + * @doesNotPerformAssertions + */ + public function testValidValueWithConfig() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $this->configProvider->method('getMaximumPageSize') + ->willReturn(10); + + $field = self::getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator->validate($field, ['pageSize' => 10]); + } + + public function testInvalidMaxValue() { $this->expectException(GraphQlInputException::class); $this->expectExceptionMessage("Maximum pageSize is 3"); - $validator = new SearchCriteriaValidator(3); + + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $field = self::getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator->validate($field, ['pageSize' => 4]); + } + + public function testInvalidValueWithConfig() + { + $this->expectException(GraphQlInputException::class); + $this->expectExceptionMessage("Maximum pageSize is 10"); + + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $this->configProvider->method('getMaximumPageSize') + ->willReturn(10); + $field = self::getMockBuilder(Field::class) ->disableOriginalConstructor() ->getMock(); - $validator->validate($field, ['pageSize' => 4]); + $this->validator->validate($field, ['pageSize' => 11]); } } diff --git a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Handler/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Handler/Iterator.php index 94a36ef0f0ba9..efab6f3289478 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Handler/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Handler/Iterator.php @@ -54,14 +54,16 @@ public function setData(array $data) * * @return Handler */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -82,24 +84,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -109,16 +114,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset)) { @@ -130,16 +137,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Iterator.php index d60b54a689791..7be45afc236e7 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/Iterator.php @@ -46,14 +46,16 @@ public function __construct(Data $configData, ConsumerConfigItemFactory $itemFac * * @return ConsumerConfigItem */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -74,24 +76,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -101,16 +106,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset)) { @@ -122,16 +129,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php index f0414edfc0c02..0941b076dba84 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php @@ -46,14 +46,16 @@ public function __construct(Data $configData, PublisherConfigItemFactory $itemFa * * @return PublisherConfigItem */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -77,24 +79,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -107,16 +112,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset) || $this->data[$offset]['disabled'] == true) { @@ -128,16 +135,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Binding/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Binding/Iterator.php index 1bc9adef61f51..662e69d040da8 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Binding/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Binding/Iterator.php @@ -54,14 +54,16 @@ public function setData(array $data) * * @return Binding */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -85,24 +87,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -115,16 +120,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset) || $this->data[$offset]['disabled'] == true) { @@ -136,16 +143,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php index 85dad637191fe..9dfdaef9be308 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php @@ -46,14 +46,16 @@ public function __construct(Data $configData, ExchangeConfigItemFactory $itemFac * * @return ExchangeConfigItem */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -74,24 +76,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -101,16 +106,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset)) { @@ -122,16 +129,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/QueueConfigItem/Iterator.php b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/QueueConfigItem/Iterator.php index e76c88f556a4f..becd5b7a7d622 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Topology/Config/QueueConfigItem/Iterator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Topology/Config/QueueConfigItem/Iterator.php @@ -45,14 +45,16 @@ public function __construct(DataMapper $configData, QueueConfigItemFactory $item * * @return QueueConfigItem */ + #[\ReturnTypeWillChange] public function current() { return $this->object; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { next($this->data); @@ -73,24 +75,27 @@ private function initObject(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { key($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { return (bool)current($this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->data); @@ -100,16 +105,18 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset)) { @@ -121,16 +128,18 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); diff --git a/lib/internal/Magento/Framework/MessageQueue/composer.json b/lib/internal/Magento/Framework/MessageQueue/composer.json index 1670adcce4ebf..f8d76f9c8611a 100644 --- a/lib/internal/Magento/Framework/MessageQueue/composer.json +++ b/lib/internal/Magento/Framework/MessageQueue/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.0.0" + "php": "~7.4.0||~8.0.0||~8.1.0" }, "autoload": { "psr-4": { diff --git a/lib/internal/Magento/Framework/ObjectManager/TMap.php b/lib/internal/Magento/Framework/ObjectManager/TMap.php index e64d80c307149..fc7138778c151 100644 --- a/lib/internal/Magento/Framework/ObjectManager/TMap.php +++ b/lib/internal/Magento/Framework/ObjectManager/TMap.php @@ -143,6 +143,7 @@ private function initObject($index) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function getIterator() { if (array_keys($this->array) != array_keys($this->objectsArray)) { @@ -157,6 +158,7 @@ public function getIterator() /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->array); @@ -165,6 +167,7 @@ public function offsetExists($offset) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return isset($this->array[$offset]) ? $this->initObject($offset) : null; @@ -173,6 +176,7 @@ public function offsetGet($offset) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->assertValidTypeLazy($value, $offset); @@ -188,6 +192,7 @@ public function offsetSet($offset, $value) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { if ($this->counter && isset($this->array[$offset])) { @@ -203,6 +208,7 @@ public function offsetUnset($offset) /** * @inheritdoc */ + #[\ReturnTypeWillChange] public function count() { return $this->counter; diff --git a/lib/internal/Magento/Framework/Phrase.php b/lib/internal/Magento/Framework/Phrase.php index 3034dfc30a9e2..1f53b0a2b71d8 100644 --- a/lib/internal/Magento/Framework/Phrase.php +++ b/lib/internal/Magento/Framework/Phrase.php @@ -1,7 +1,5 @@ <?php /** - * Phrase (for replacing Data Value with Object) - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -11,6 +9,8 @@ use Magento\Framework\Phrase\RendererInterface; /** + * Phrase (for replacing Data Value with Object) + * * @api * @since 100.0.2 */ @@ -122,6 +122,7 @@ public function __toString() * * @return string */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->render(); diff --git a/lib/internal/Magento/Framework/Pricing/Price/Collection.php b/lib/internal/Magento/Framework/Pricing/Price/Collection.php index eedb910c56b92..51ef59bf416fc 100644 --- a/lib/internal/Magento/Framework/Pricing/Price/Collection.php +++ b/lib/internal/Magento/Framework/Pricing/Price/Collection.php @@ -9,7 +9,7 @@ use Magento\Framework\Pricing\SaleableInterface; /** - * Class Collection + * The Price Collection * * @api * @since 100.0.2 @@ -79,6 +79,7 @@ public function __construct( * * @return mixed|void */ + #[\ReturnTypeWillChange] public function rewind() { return $this->pool->rewind(); @@ -89,6 +90,7 @@ public function rewind() * * @return \Magento\Framework\Pricing\Price\PriceInterface */ + #[\ReturnTypeWillChange] public function current() { return $this->get($this->key()); @@ -99,6 +101,7 @@ public function current() * * @return string */ + #[\ReturnTypeWillChange] public function key() { return $this->pool->key(); @@ -109,6 +112,7 @@ public function key() * * @return mixed|void */ + #[\ReturnTypeWillChange] public function next() { return $this->pool->next(); @@ -119,6 +123,7 @@ public function next() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return $this->pool->valid(); diff --git a/lib/internal/Magento/Framework/Pricing/Price/Pool.php b/lib/internal/Magento/Framework/Pricing/Price/Pool.php index a1c7d416b2259..079311c723d69 100644 --- a/lib/internal/Magento/Framework/Pricing/Price/Pool.php +++ b/lib/internal/Magento/Framework/Pricing/Price/Pool.php @@ -7,7 +7,7 @@ namespace Magento\Framework\Pricing\Price; /** - * Class Pool + * The price pool * * @api * @since 100.0.2 @@ -40,6 +40,7 @@ public function __construct( * * @return mixed */ + #[\ReturnTypeWillChange] public function rewind() { return reset($this->prices); @@ -50,6 +51,7 @@ public function rewind() * * @return mixed */ + #[\ReturnTypeWillChange] public function current() { return current($this->prices); @@ -60,6 +62,7 @@ public function current() * * @return string */ + #[\ReturnTypeWillChange] public function key() { return key($this->prices); @@ -70,6 +73,7 @@ public function key() * * @return mixed */ + #[\ReturnTypeWillChange] public function next() { return next($this->prices); @@ -80,6 +84,7 @@ public function next() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return (bool)$this->key(); @@ -103,6 +108,7 @@ public function get($code) * @param string $value * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { @@ -118,6 +124,7 @@ public function offsetSet($offset, $value) * @param string $offset * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->prices[$offset]); @@ -129,6 +136,7 @@ public function offsetExists($offset) * @param string $offset * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->prices[$offset]); @@ -140,6 +148,7 @@ public function offsetUnset($offset) * @param string $offset * @return string */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->prices[$offset] ?? null; diff --git a/lib/internal/Magento/Framework/Search/Response/Aggregation.php b/lib/internal/Magento/Framework/Search/Response/Aggregation.php index 7ebe052b65ba8..06fbc0f1e675e 100644 --- a/lib/internal/Magento/Framework/Search/Response/Aggregation.php +++ b/lib/internal/Magento/Framework/Search/Response/Aggregation.php @@ -35,6 +35,7 @@ public function __construct(array $buckets) * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->buckets); diff --git a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php index d7d0a8d03b28c..a45c3452a3c5c 100644 --- a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php +++ b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php @@ -52,6 +52,7 @@ public function __construct(array $documents, AggregationInterface $aggregations * * @return int */ + #[\ReturnTypeWillChange] public function count() { return count($this->documents); @@ -62,6 +63,7 @@ public function count() * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->documents); diff --git a/lib/internal/Magento/Framework/Session/SaveHandler.php b/lib/internal/Magento/Framework/Session/SaveHandler.php index 5891b8880b627..c21ccab042373 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler.php @@ -76,6 +76,7 @@ public function __construct( * @param string $name * @return bool */ + #[\ReturnTypeWillChange] public function open($savePath, $name) { return $this->callSafely('open', $savePath, $name); @@ -86,6 +87,7 @@ public function open($savePath, $name) * * @return bool */ + #[\ReturnTypeWillChange] public function close() { return $this->callSafely('close'); @@ -97,6 +99,7 @@ public function close() * @param string $sessionId * @return string */ + #[\ReturnTypeWillChange] public function read($sessionId) { return $this->callSafely('read', $sessionId); @@ -110,6 +113,7 @@ public function read($sessionId) * @return bool * @throws LocalizedException */ + #[\ReturnTypeWillChange] public function write($sessionId, $data) { $sessionMaxSize = $this->sessionMaxSizeConfig->getSessionMaxSize(); @@ -136,6 +140,7 @@ public function write($sessionId, $data) * @param string $sessionId * @return bool */ + #[\ReturnTypeWillChange] public function destroy($sessionId) { return $this->callSafely('destroy', $sessionId); @@ -148,6 +153,7 @@ public function destroy($sessionId) * @return bool * @SuppressWarnings(PHPMD.ShortMethodName) */ + #[\ReturnTypeWillChange] public function gc($maxLifetime) { return $this->callSafely('gc', $maxLifetime); diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php b/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php index 2f5a5a8610bf9..6f1bb9442978b 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php @@ -83,6 +83,7 @@ protected function checkConnection() * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { return true; @@ -93,6 +94,7 @@ public function open($savePath, $sessionName) * * @return bool */ + #[\ReturnTypeWillChange] public function close() { return true; @@ -104,6 +106,7 @@ public function close() * @param string $sessionId * @return string */ + #[\ReturnTypeWillChange] public function read($sessionId) { // need to use write connection to get the most fresh DB sessions @@ -131,6 +134,7 @@ public function read($sessionId) * @param string $sessionData * @return bool */ + #[\ReturnTypeWillChange] public function write($sessionId, $sessionData) { $hashedSessionId = $this->encryptor->hash($sessionId); @@ -158,6 +162,7 @@ public function write($sessionId, $sessionData) * @param string $sessionId * @return bool */ + #[\ReturnTypeWillChange] public function destroy($sessionId) { $where = ['session_id = ?' => $this->encryptor->hash($sessionId)]; @@ -172,6 +177,7 @@ public function destroy($sessionId) * @return bool * @SuppressWarnings(PHPMD.ShortMethodName) */ + #[\ReturnTypeWillChange] public function gc($maxLifeTime) { $where = ['session_expires < ?' => time() - $maxLifeTime]; diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Native.php b/lib/internal/Magento/Framework/Session/SaveHandler/Native.php index 95238482e19ce..207af54e9abf6 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Native.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Native.php @@ -12,13 +12,15 @@ class Native extends \SessionHandler { /** * Workaround for php7 session_regenerate_id error + * * @see https://bugs.php.net/bug.php?id=71187 * * @param string $sessionId * @return string */ + #[\ReturnTypeWillChange] public function read($sessionId) { - return (string)parent::read($sessionId); + return (string) parent::read($sessionId); } } diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php index cd1cef5da6ddd..d4ace455cdd8b 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php @@ -76,6 +76,7 @@ private function getConnection() * @return bool * @throws SessionException */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { return $this->getConnection()->open($savePath, $sessionName); @@ -85,17 +86,21 @@ public function open($savePath, $sessionName) * Fetch session data * * @param string $sessionId - * @return string - * @throws ConcurrentConnectionsExceededException + * @return string|false * @throws SessionException */ + #[\ReturnTypeWillChange] public function read($sessionId) { + $result = false; + try { - return $this->getConnection()->read($sessionId); + $result = $this->getConnection()->read($sessionId); } catch (ConcurrentConnectionsExceededException $e) { require $this->filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php'); } + + return $result; } /** @@ -106,6 +111,7 @@ public function read($sessionId) * @return boolean * @throws SessionException */ + #[\ReturnTypeWillChange] public function write($sessionId, $sessionData) { return $this->getConnection()->write($sessionId, $sessionData); @@ -118,6 +124,7 @@ public function write($sessionId, $sessionData) * @return boolean * @throws SessionException */ + #[\ReturnTypeWillChange] public function destroy($sessionId) { return $this->getConnection()->destroy($sessionId); @@ -129,6 +136,7 @@ public function destroy($sessionId) * @return bool * @throws SessionException */ + #[\ReturnTypeWillChange] public function close() { return $this->getConnection()->close(); @@ -142,6 +150,7 @@ public function close() * @throws SessionException * @SuppressWarnings(PHPMD.ShortMethodName) */ + #[\ReturnTypeWillChange] public function gc($maxLifeTime) { return $this->getConnection()->gc($maxLifeTime); diff --git a/lib/internal/Magento/Framework/Setup/Patch/PatchRegistry.php b/lib/internal/Magento/Framework/Setup/Patch/PatchRegistry.php index 25f2bb64d8100..00dce4971a151 100644 --- a/lib/internal/Magento/Framework/Setup/Patch/PatchRegistry.php +++ b/lib/internal/Magento/Framework/Setup/Patch/PatchRegistry.php @@ -195,6 +195,7 @@ public function getReverseIterator() * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { if ($this->iterator === null) { diff --git a/lib/internal/Magento/Framework/Simplexml/Element.php b/lib/internal/Magento/Framework/Simplexml/Element.php index 268eb8cd636e3..e94ef95ef761d 100644 --- a/lib/internal/Magento/Framework/Simplexml/Element.php +++ b/lib/internal/Magento/Framework/Simplexml/Element.php @@ -66,6 +66,7 @@ public function getParent() * @return boolean * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ + #[\ReturnTypeWillChange] public function hasChildren() { if (!$this->children()) { diff --git a/lib/internal/Magento/Framework/Stdlib/StringUtils.php b/lib/internal/Magento/Framework/Stdlib/StringUtils.php index a3c31e734a20e..73a254d6a2d04 100644 --- a/lib/internal/Magento/Framework/Stdlib/StringUtils.php +++ b/lib/internal/Magento/Framework/Stdlib/StringUtils.php @@ -149,7 +149,7 @@ public function split($value, $length = 1, $keepWords = false, $trim = false, $w */ public function strlen($string) { - return mb_strlen($string, self::ICONV_CHARSET); + return $string !== null ? mb_strlen($string, self::ICONV_CHARSET) : 0; } /** diff --git a/lib/internal/Magento/Framework/View/Asset/Merged.php b/lib/internal/Magento/Framework/View/Asset/Merged.php index 77f6b4d20a314..2574391af7f32 100644 --- a/lib/internal/Magento/Framework/View/Asset/Merged.php +++ b/lib/internal/Magento/Framework/View/Asset/Merged.php @@ -15,7 +15,7 @@ class Merged implements \Iterator /** * Directory for dynamically generated public view files, relative to STATIC_VIEW */ - const CACHE_VIEW_REL = '_cache'; + public const CACHE_VIEW_REL = '_cache'; /** * @var \Psr\Log\LoggerInterface @@ -143,10 +143,11 @@ private function createMergedAsset(array $assets) } /** - * {@inheritdoc} + * @inheritdoc * * @return AssetInterface */ + #[\ReturnTypeWillChange] public function current() { $this->initialize(); @@ -154,8 +155,9 @@ public function current() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function key() { $this->initialize(); @@ -163,8 +165,9 @@ public function key() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function next() { $this->initialize(); @@ -172,8 +175,9 @@ public function next() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function rewind() { $this->initialize(); @@ -181,8 +185,9 @@ public function rewind() } /** - * {@inheritdoc} + * @inheritdoc */ + #[\ReturnTypeWillChange] public function valid() { $this->initialize(); diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Document.php b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Document.php index 1562f879ec6b6..b9585b561c0f3 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Document.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Document.php @@ -10,7 +10,7 @@ use Magento\Framework\Api\AttributeValueFactory; /** - * Class Document + * The document data provider */ class Document extends DataObject implements DocumentInterface { @@ -33,17 +33,23 @@ public function __construct(AttributeValueFactory $attributeValueFactory) } /** + * Gets ID. + * * @return int|string */ public function getId() { if (!$this->id) { - $this->id = $this->getData($this->getIdFieldName()); + $this->id = $this->getIdFieldName() !== null + ? $this->getData($this->getIdFieldName()) + : null; } return $this->id; } /** + * Sets ID. + * * @param int $id * @return void */ diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index d6a782d9197d8..ba8fc7f779896 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -11,6 +11,7 @@ use Magento\Framework\View; use Magento\Framework\Escaper; use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\ScopeInterface; /** * An API for page configuration @@ -33,29 +34,29 @@ class Config /**#@+ * Constants of available types */ - const ELEMENT_TYPE_BODY = 'body'; - const ELEMENT_TYPE_HTML = 'html'; - const ELEMENT_TYPE_HEAD = 'head'; + public const ELEMENT_TYPE_BODY = 'body'; + public const ELEMENT_TYPE_HTML = 'html'; + public const ELEMENT_TYPE_HEAD = 'head'; /**#@-*/ - const META_DESCRIPTION = 'description'; - const META_CONTENT_TYPE = 'content_type'; - const META_MEDIA_TYPE = 'media_type'; - const META_CHARSET = 'charset'; - const META_TITLE = 'title'; - const META_KEYWORDS = 'keywords'; - const META_ROBOTS = 'robots'; - const META_X_UI_COMPATIBLE = 'x_ua_compatible'; + public const META_DESCRIPTION = 'description'; + public const META_CONTENT_TYPE = 'content_type'; + public const META_MEDIA_TYPE = 'media_type'; + public const META_CHARSET = 'charset'; + public const META_TITLE = 'title'; + public const META_KEYWORDS = 'keywords'; + public const META_ROBOTS = 'robots'; + public const META_X_UI_COMPATIBLE = 'x_ua_compatible'; /** * Constant body attribute class */ - const BODY_ATTRIBUTE_CLASS = 'class'; + public const BODY_ATTRIBUTE_CLASS = 'class'; /** * Constant html language attribute */ - const HTML_ATTRIBUTE_LANG = 'lang'; + public const HTML_ATTRIBUTE_LANG = 'lang'; /** * @var Escaper @@ -192,10 +193,11 @@ public function __construct( $this->title = $title; $this->localeResolver = $localeResolver; $this->isIncludesAvailable = $isIncludesAvailable; + $locale = $this->localeResolver->getLocale(); $this->setElementAttribute( self::ELEMENT_TYPE_HTML, self::HTML_ATTRIBUTE_LANG, - strstr($this->localeResolver->getLocale(), '_', true) + $locale !== null ? strstr($locale, '_', true) : false ); $this->escaper = $escaper ?? ObjectManager::getInstance()->get( Escaper::class @@ -321,7 +323,7 @@ public function getMediaType() if (empty($this->metadata[self::META_MEDIA_TYPE])) { $this->metadata[self::META_MEDIA_TYPE] = $this->scopeConfig->getValue( 'design/head/default_media_type', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } return $this->metadata[self::META_MEDIA_TYPE]; @@ -349,7 +351,7 @@ public function getCharset() if (empty($this->metadata[self::META_CHARSET])) { $this->metadata[self::META_CHARSET] = $this->scopeConfig->getValue( 'design/head/default_charset', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } return $this->metadata[self::META_CHARSET]; @@ -377,7 +379,7 @@ public function getDescription() if (empty($this->metadata[self::META_DESCRIPTION])) { $this->metadata[self::META_DESCRIPTION] = $this->scopeConfig->getValue( 'design/head/default_description', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } return $this->metadata[self::META_DESCRIPTION]; @@ -432,7 +434,7 @@ public function getKeywords() if (empty($this->metadata[self::META_KEYWORDS])) { $this->metadata[self::META_KEYWORDS] = $this->scopeConfig->getValue( 'design/head/default_keywords', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } return $this->metadata[self::META_KEYWORDS]; @@ -464,7 +466,7 @@ public function getRobots() if (empty($this->metadata[self::META_ROBOTS])) { $this->metadata[self::META_ROBOTS] = $this->scopeConfig->getValue( 'design/search_engine_robots/default_robots', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } return $this->metadata[self::META_ROBOTS]; @@ -651,12 +653,12 @@ public function getDefaultFavicon() */ public function getIncludes() { - if (empty($this->includes) && $this->isIncludesAvailable) { + if ($this->includes === null && $this->isIncludesAvailable) { $this->includes = $this->scopeConfig->getValue( 'design/head/includes', \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); } - return $this->includes; + return $this->includes ??= ''; } } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 1e2c8ab377bcb..908a4e701406f 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -1,7 +1,5 @@ <?php /** - * Service Input Processor - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -24,6 +22,7 @@ use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Laminas\Code\Reflection\ClassReflection; +use Magento\Framework\Webapi\Validator\IOLimit\DefaultPageSizeSetter; use Magento\Framework\Webapi\Validator\ServiceInputValidatorInterface; /** @@ -35,7 +34,7 @@ */ class ServiceInputProcessor implements ServicePayloadConverterInterface { - const EXTENSION_ATTRIBUTES_TYPE = \Magento\Framework\Api\ExtensionAttributesInterface::class; + public const EXTENSION_ATTRIBUTES_TYPE = \Magento\Framework\Api\ExtensionAttributesInterface::class; /** * @var \Magento\Framework\Reflection\TypeProcessor @@ -97,6 +96,11 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $defaultPageSize; + /** + * @var DefaultPageSizeSetter|null + */ + private $defaultPageSizeSetter; + /** * Initialize dependencies. * @@ -110,6 +114,8 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param array $customAttributePreprocessors * @param ServiceInputValidatorInterface|null $serviceInputValidator * @param int $defaultPageSize + * @param DefaultPageSizeSetter|null $defaultPageSizeSetter + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( TypeProcessor $typeProcessor, @@ -121,7 +127,8 @@ public function __construct( ConfigInterface $config = null, array $customAttributePreprocessors = [], ServiceInputValidatorInterface $serviceInputValidator = null, - int $defaultPageSize = 20 + int $defaultPageSize = 20, + ?DefaultPageSizeSetter $defaultPageSizeSetter = null ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -136,6 +143,8 @@ public function __construct( $this->serviceInputValidator = $serviceInputValidator ?: ObjectManager::getInstance()->get(ServiceInputValidatorInterface::class); $this->defaultPageSize = $defaultPageSize >= 10 ? $defaultPageSize : 10; + $this->defaultPageSizeSetter = $defaultPageSizeSetter ?? ObjectManager::getInstance() + ->get(DefaultPageSizeSetter::class); } /** @@ -309,10 +318,8 @@ protected function _createFromArray($className, $data) } } - if ($object instanceof SearchCriteriaInterface - && $object->getPageSize() === null - ) { - $object->setPageSize($this->defaultPageSize); + if ($object instanceof SearchCriteriaInterface) { + $this->defaultPageSizeSetter->processSearchCriteria($object, $this->defaultPageSize); } return $object; diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/InputLimit/DefaultPageSizeSetterTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/InputLimit/DefaultPageSizeSetterTest.php new file mode 100644 index 0000000000000..0a0a63d5154c3 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/InputLimit/DefaultPageSizeSetterTest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Webapi\Test\Unit\InputLimit; + +use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\Webapi\Validator\IOLimit\DefaultPageSizeSetter; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test the page size is correctly set + */ +class DefaultPageSizeSetterTest extends TestCase +{ + /** + * @var IOLimitConfigProvider|MockObject + */ + private $configProvider; + + /** + * @var DefaultPageSizeSetter + */ + private $setter; + + protected function setUp(): void + { + $this->configProvider = $this->getMockBuilder(IOLimitConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->setter = new DefaultPageSizeSetter($this->configProvider); + } + + /** + * @doesNotPerformAssertions + */ + public function testPageSizeIsNotSetWhenLimitingIsDisabled() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(false); + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->getMock(); + $searchCriteria->method('getPageSize') + ->willReturn(null); + $searchCriteria->expects(self::never()) + ->method('setPageSize'); + + $this->setter->processSearchCriteria($searchCriteria); + } + + /** + * @doesNotPerformAssertions + */ + public function testPageSizeIsNotSetWhenAlreadySet() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->getMock(); + $searchCriteria->method('getPageSize') + ->willReturn(123); + $searchCriteria->expects(self::never()) + ->method('setPageSize'); + + $this->setter->processSearchCriteria($searchCriteria); + } + + /** + * @doesNotPerformAssertions + */ + public function testPageSizeIsSetWithPreferredConfigValue() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $this->configProvider->method('getDefaultPageSize') + ->willReturn(456); + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->getMock(); + $searchCriteria->method('getPageSize') + ->willReturn(null); + + $searchCriteria->expects(self::once()) + ->method('setPageSize') + ->with(456); + + $this->setter->processSearchCriteria($searchCriteria, 678); + } + + /** + * @doesNotPerformAssertions + */ + public function testPageSizeIsSetWithPreferredFallbackValue() + { + $this->configProvider->method('isInputLimitingEnabled') + ->willReturn(true); + $this->configProvider->method('getDefaultPageSize') + ->willReturn(null); + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->getMock(); + $searchCriteria->method('getPageSize') + ->willReturn(null); + + $searchCriteria->expects(self::once()) + ->method('setPageSize') + ->with(678); + + $this->setter->processSearchCriteria($searchCriteria, 678); + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php index 7ddeda2751005..85882129da073 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php @@ -7,10 +7,12 @@ namespace Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor; +use Magento\Framework\Api\SearchCriteriaInterface; + class TestService { - const DEFAULT_VALUE = 42; - const CUSTOM_ATTRIBUTE_CODE = 'customAttr'; + public const DEFAULT_VALUE = 42; + public const CUSTOM_ATTRIBUTE_CODE = 'customAttr'; /** * @param int $entityId @@ -85,6 +87,15 @@ public function dataArray(array $dataObjects) return $dataObjects; } + /** + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * @return \Magento\Framework\Api\SearchCriteriaInterface + */ + public function search(SearchCriteriaInterface $searchCriteria) + { + return $searchCriteria; + } + /** * @param \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray $arrayData * @return \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index 53540196d5d9b..6aef1a61922c8 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -10,6 +10,7 @@ use Magento\Eav\Model\TypeLocator; use Magento\Framework\Api\AttributeValue; use Magento\Framework\Api\AttributeValueFactory; +use \Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\App\Cache\Type\Reflection; use Magento\Framework\Exception\InvalidArgumentException; use Magento\Framework\ObjectManagerInterface; @@ -19,7 +20,9 @@ use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Webapi\Validator\IOLimit\DefaultPageSizeSetter; use Magento\Framework\Webapi\ServiceInputProcessor; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\Webapi\Validator\EntityArrayValidator; use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray; @@ -60,13 +63,37 @@ class ServiceInputProcessorTest extends TestCase /** @var MockObject */ protected $fieldNamer; + /** + * @var SearchCriteriaInterface|MockObject + */ + private $searchCriteria; + /** * @var ServiceTypeToEntityTypeMap|MockObject */ private $serviceTypeToEntityTypeMap; + /** + * @var IOLimitConfigProvider|MockObject + */ + private $inputLimitConfig; + + /** + * @var DefaultPageSizeSetter|MockObject + */ + private $defaultPageSizeSetter; + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ protected function setUp(): void { + $this->searchCriteria = self::getMockBuilder(SearchCriteriaInterface::class) + ->getMock(); + + $objectManagerStatic = [ + SearchCriteriaInterface::class => $this->searchCriteria + ]; $objectManager = new ObjectManager($this); $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->disableOriginalConstructor() @@ -74,7 +101,11 @@ protected function setUp(): void $this->objectManagerMock->expects($this->any()) ->method('create') ->willReturnCallback( - function ($className, $arguments = []) use ($objectManager) { + function ($className, $arguments = []) use ($objectManager, $objectManagerStatic) { + if (isset($objectManagerStatic[$className])) { + return $objectManagerStatic[$className]; + } + return $objectManager->getObject($className, $arguments); } ); @@ -132,6 +163,14 @@ function () use ($objectManager) { ->disableOriginalConstructor() ->getMock(); + $this->inputLimitConfig = self::getMockBuilder(IOLimitConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->defaultPageSizeSetter = self::getMockBuilder(DefaultPageSizeSetter::class) + ->disableOriginalConstructor() + ->getMock(); + $this->serviceInputProcessor = $objectManager->getObject( ServiceInputProcessor::class, [ @@ -141,7 +180,9 @@ function () use ($objectManager) { 'attributeValueFactory' => $this->attributeValueFactoryMock, 'methodsMap' => $this->methodsMap, 'serviceTypeToEntityTypeMap' => $this->serviceTypeToEntityTypeMap, - 'serviceInputValidator' => new EntityArrayValidator(50) + 'serviceInputValidator' => new EntityArrayValidator(50, $this->inputLimitConfig), + 'defaultPageSizeSetter' => $this->defaultPageSizeSetter, + 'defaultPageSize' => 123 ] ); @@ -340,6 +381,8 @@ public function testArrayOfDataObjectPropertiesIsValidated() $this->expectErrorMessage( 'Maximum items of type "\\' . Simple::class . '" is 50' ); + $this->inputLimitConfig->method('isInputLimitingEnabled') + ->willReturn(true); $objects = []; for ($i = 0; $i < 51; $i++) { $objects[] = ['entityId' => $i + 1, 'name' => 'Item' . $i]; @@ -354,6 +397,25 @@ public function testArrayOfDataObjectPropertiesIsValidated() ); } + /** + * @doesNotPerformAssertions + */ + public function testDefaultPageSizeSetterIsInvoked() + { + $this->defaultPageSizeSetter->expects(self::once()) + ->method('processSearchCriteria') + ->with($this->searchCriteria); + + $data = [ + 'searchCriteria' => [] + ]; + $this->serviceInputProcessor->process( + TestService::class, + 'search', + $data + ); + } + public function testNestedSimpleArrayProperties() { $data = ['arrayData' => ['ids' => [1, 2, 3, 4]]]; diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php index 5a2a69e45a3b9..6185e80cd342f 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php @@ -9,7 +9,9 @@ namespace Magento\Framework\Webapi\Test\Unit\Validator; use Magento\Framework\Exception\InvalidArgumentException; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\Webapi\Validator\EntityArrayValidator; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -17,20 +19,73 @@ */ class EntityArrayValidatorTest extends TestCase { + /** + * @var IOLimitConfigProvider|MockObject + */ + private $config; + + /** + * @var EntityArrayValidator + */ + private $validator; + + protected function setUp(): void + { + $this->config = self::getMockBuilder(IOLimitConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator = new EntityArrayValidator(3, $this->config); + } + /** * @doesNotPerformAssertions */ public function testAllowsDataWhenBelowLimit() { - $validator = new EntityArrayValidator(3); - $validator->validateComplexArrayType("foo", [[],[],[]]); + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->validator->validateComplexArrayType("foo", [[],[],[]]); + } + + /** + * @doesNotPerformAssertions + */ + public function testAllowsDataWhenBelowLimitUsingConfig() + { + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->config->method('getComplexArrayItemLimit') + ->willReturn(6); + $this->validator->validateComplexArrayType("foo", [[],[],[],[],[]]); } public function testFailsDataWhenAboveLimit() { $this->expectException(InvalidArgumentException::class); $this->expectErrorMessage('Maximum items of type "foo" is 3'); - $validator = new EntityArrayValidator(3); - $validator->validateComplexArrayType("foo", [[],[],[],[]]); + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->validator->validateComplexArrayType("foo", [[],[],[],[]]); + } + + public function testFailsDataWhenAboveLimitUsingConfig() + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Maximum items of type "foo" is 6'); + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->config->method('getComplexArrayItemLimit') + ->willReturn(6); + $this->validator->validateComplexArrayType("foo", [[],[],[],[],[],[],[]]); + } + + /** + * @doesNotPerformAssertions + */ + public function testAboveLimitWithDisabledLimiting() + { + $this->config->method('isInputLimitingEnabled') + ->willReturn(false); + $this->validator->validateComplexArrayType("foo", [[],[],[],[],[],[],[]]); } } diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/SearchCriteriaValidatorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/SearchCriteriaValidatorTest.php index 8524061dc4f60..df9d14c2786d0 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/SearchCriteriaValidatorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/SearchCriteriaValidatorTest.php @@ -10,7 +10,9 @@ use Magento\Framework\Api\SearchCriteria; use Magento\Framework\Exception\InvalidArgumentException; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\Webapi\Validator\SearchCriteriaValidator; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -18,22 +20,75 @@ */ class SearchCriteriaValidatorTest extends TestCase { + /** + * @var IOLimitConfigProvider|MockObject + */ + private $config; + + /** + * @var SearchCriteriaValidator + */ + private $validator; + + protected function setUp(): void + { + $this->config = self::getMockBuilder(IOLimitConfigProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validator = new SearchCriteriaValidator(3, $this->config); + } + /** * @doesNotPerformAssertions */ public function testAllowsPageSizeWhenAboveMinLimitAndBelowMaxLimit() { - $searchCriteria = new SearchCriteria(); - $validator = new SearchCriteriaValidator(3); - $validator->validateEntityValue($searchCriteria, 'pageSize', 2); + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->validator->validateEntityValue(new SearchCriteria(), 'pageSize', 2); + } + + /** + * @doesNotPerformAssertions + */ + public function testAllowsPageSizeWhenAboveMinLimitAndBelowMaxLimitUsingConfig() + { + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->config->method('getMaximumPageSize') + ->willReturn(50); + $this->validator->validateEntityValue(new SearchCriteria(), 'pageSize', 25); + } + + /** + * @doesNotPerformAssertions + */ + public function testDisabledLimiting() + { + $this->config->method('isInputLimitingEnabled') + ->willReturn(false); + $this->validator->validateEntityValue(new SearchCriteria(), 'pageSize', 1000); } public function testFailsPageSizeWhenAboveMaxLimit() { $this->expectException(InvalidArgumentException::class); $this->expectErrorMessage('Maximum SearchCriteria pageSize is 3'); - $searchCriteria = new SearchCriteria(); - $validator = new SearchCriteriaValidator(3); - $validator->validateEntityValue($searchCriteria, 'pageSize', 4); + + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->validator->validateEntityValue(new SearchCriteria(), 'pageSize', 4); + } + + public function testFailsPageSizeWhenAboveMaxLimitUsingConfig() + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Maximum SearchCriteria pageSize is 50'); + + $this->config->method('isInputLimitingEnabled') + ->willReturn(true); + $this->config->method('getMaximumPageSize') + ->willReturn(50); + $this->validator->validateEntityValue(new SearchCriteria(), 'pageSize', 100); } } diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 5502179e495be..91019006af301 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -8,7 +8,9 @@ namespace Magento\Framework\Webapi\Validator; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\InvalidArgumentException; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; /** * Validates service input @@ -20,12 +22,20 @@ class EntityArrayValidator implements ServiceInputValidatorInterface */ private $complexArrayItemLimit; + /** + * @var IOLimitConfigProvider|null + */ + private $configProvider; + /** * @param int $complexArrayItemLimit + * @param IOLimitConfigProvider|null $configProvider */ - public function __construct(int $complexArrayItemLimit) + public function __construct(int $complexArrayItemLimit, ?IOLimitConfigProvider $configProvider = null) { $this->complexArrayItemLimit = $complexArrayItemLimit; + $this->configProvider = $configProvider ?? ObjectManager::getInstance() + ->get(IOLimitConfigProvider::class); } /** @@ -33,11 +43,17 @@ public function __construct(int $complexArrayItemLimit) */ public function validateComplexArrayType(string $className, array $items): void { - if (count($items) > $this->complexArrayItemLimit) { + if (!$this->configProvider->isInputLimitingEnabled()) { + return; + } + + $max = $this->configProvider->getComplexArrayItemLimit() ?? $this->complexArrayItemLimit; + + if (count($items) > $max) { throw new InvalidArgumentException( __( 'Maximum items of type "%type" is %max', - ['type' => $className, 'max' => $this->complexArrayItemLimit] + ['type' => $className, 'max' => $max] ) ); } diff --git a/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/DefaultPageSizeSetter.php b/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/DefaultPageSizeSetter.php new file mode 100644 index 0000000000000..dcc97ecb7670e --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/DefaultPageSizeSetter.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Webapi\Validator\IOLimit; + +use Magento\Framework\Api\SearchCriteriaInterface; + +/** + * Sets the default page size with the configured input limits + */ +class DefaultPageSizeSetter +{ + /** + * @var IOLimitConfigProvider + */ + private $validationConfigProvider; + + /** + * @param IOLimitConfigProvider $validationConfigProvider + */ + public function __construct(IOLimitConfigProvider $validationConfigProvider) + { + $this->validationConfigProvider = $validationConfigProvider; + } + + /** + * Set the default page size if needed using the optional parameter as a fallback value + * + * @param SearchCriteriaInterface $searchCriteria The search criteria to manipulate + * @param int|null $defaultPageSizeFallback The fallback value if limiting is enabled but a limit has not been set + */ + public function processSearchCriteria( + SearchCriteriaInterface $searchCriteria, + int $defaultPageSizeFallback = null + ): void { + if ($searchCriteria->getPageSize() === null + && $this->validationConfigProvider->isInputLimitingEnabled() + ) { + $searchCriteria->setPageSize( + $this->validationConfigProvider->getDefaultPageSize() ?? $defaultPageSizeFallback + ); + } + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/IOLimitConfigProvider.php b/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/IOLimitConfigProvider.php new file mode 100644 index 0000000000000..ac94c5393b099 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Validator/IOLimit/IOLimitConfigProvider.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Webapi\Validator\IOLimit; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Provides configuration related to the WebApi input limit validation + */ +class IOLimitConfigProvider +{ + /** + * Path to the configuration setting for if the input limiting is enabled + */ + public const CONFIG_PATH_INPUT_LIMIT_ENABLED = 'webapi/validation/input_limit_enabled'; + + /** + * Path to the configuration setting for maximum complex array items + */ + public const CONFIG_PATH_COMPLEX_ARRAY_ITEM = 'webapi/validation/complex_array_limit'; + + /** + * Path to the configuration setting for maximum page size allowed + */ + public const CONFIG_PATH_MAXIMUM_PAGE_SIZE = 'webapi/validation/maximum_page_size'; + + /** + * Path to the configuration setting for default page size + */ + public const CONFIG_PATH_DEFAULT_PAGE_SIZE = 'webapi/validation/default_page_size'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritDoc + */ + public function isInputLimitingEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_INPUT_LIMIT_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get the stored configuration for the maximum number of items allowed in an array property of an entity + */ + public function getComplexArrayItemLimit(): ?int + { + $value = $this->scopeConfig->getValue( + self::CONFIG_PATH_COMPLEX_ARRAY_ITEM, + ScopeInterface::SCOPE_STORE + ); + return isset($value) ? (int)$value : null; + } + + /** + * Get the stored configuration for the maxmimum page size + */ + public function getMaximumPageSize(): ?int + { + $value = $this->scopeConfig->getValue( + self::CONFIG_PATH_MAXIMUM_PAGE_SIZE, + ScopeInterface::SCOPE_STORE + ); + return isset($value) ? (int)$value : null; + } + + /** + * Get the stored configuration for the default page size + */ + public function getDefaultPageSize(): ?int + { + $value = $this->scopeConfig->getValue( + self::CONFIG_PATH_DEFAULT_PAGE_SIZE, + ScopeInterface::SCOPE_STORE + ); + return isset($value) ? (int)$value : null; + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Validator/SearchCriteriaValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/SearchCriteriaValidator.php index 8ad6914f05d5c..12a88700cc492 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/SearchCriteriaValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/SearchCriteriaValidator.php @@ -9,7 +9,9 @@ namespace Magento\Framework\Webapi\Validator; use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\InvalidArgumentException; +use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; /** * Validates search criteria inputs @@ -21,12 +23,20 @@ class SearchCriteriaValidator implements ServiceInputValidatorInterface */ private $maximumPageSize; + /** + * @var IOLimitConfigProvider|null + */ + private $configProvider; + /** * @param int $maximumPageSize + * @param IOLimitConfigProvider|null $configProvider */ - public function __construct(int $maximumPageSize) + public function __construct(int $maximumPageSize, ?IOLimitConfigProvider $configProvider = null) { $this->maximumPageSize = $maximumPageSize; + $this->configProvider = $configProvider ?? ObjectManager::getInstance() + ->get(IOLimitConfigProvider::class); } /** @@ -44,10 +54,11 @@ public function validateEntityValue(object $entity, string $propertyName, $value { if ($entity instanceof SearchCriteriaInterface && $propertyName === 'pageSize' - && $value > $this->maximumPageSize + && $this->configProvider->isInputLimitingEnabled() + && $value > ($max = $this->configProvider->getMaximumPageSize() ?? $this->maximumPageSize) ) { throw new InvalidArgumentException( - __('Maximum SearchCriteria pageSize is %max', ['max' => $this->maximumPageSize]) + __('Maximum SearchCriteria pageSize is %max', ['max' => $max]) ); } } diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index aefe1ced821c9..a012f934463d3 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -10,7 +10,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.0.0", + "php": "~7.4.0||~8.0.0||~8.1.0", "ext-bcmath": "*", "ext-curl": "*", "ext-dom": "*", @@ -34,7 +34,7 @@ "laminas/laminas-stdlib": "^3.6.0", "laminas/laminas-uri": "^2.9.1", "laminas/laminas-validator": "^2.15.0", - "magento/zendframework1": "dev-master as 1.14.6", + "magento/zendframework1": "1.14.6-beta2 as 1.14.6", "monolog/monolog": "^2.3", "ramsey/uuid": "~4.2.0", "symfony/console": "~4.4.0", diff --git a/lib/web/css/docs/source/js/dropdown.js b/lib/web/css/docs/source/js/dropdown.js index 36c7e998a582a..92c13703959c9 100644 --- a/lib/web/css/docs/source/js/dropdown.js +++ b/lib/web/css/docs/source/js/dropdown.js @@ -63,7 +63,7 @@ }); }; - $(document).ready(function () { + $(function () { $('[data-toggle=dropdown]').dropdown(); }); diff --git a/lib/web/jquery/bootstrap/collapse.js b/lib/web/jquery/bootstrap/collapse.js new file mode 100644 index 0000000000000..95e28cec248ed --- /dev/null +++ b/lib/web/jquery/bootstrap/collapse.js @@ -0,0 +1,369 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): collapse.js and base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([ + "jquery", + "./util/index", + "./dom/data", + "./dom/event-handler", + "./dom/manipulator", + "./dom/selector-engine" +], function($, Util, Data, EventHandler, Manipulator, SelectorEngine) { + 'use strict'; + + const defineJQueryPlugin = Util.defineJQueryPlugin; + const executeAfterTransition = Util.executeAfterTransition; + const getElement = Util.getElement; + const getSelectorFromElement = Util.getSelectorFromElement; + const getElementFromSelector = Util.getElementFromSelector; + const reflow = Util.reflow; + const typeCheckConfig = Util.typeCheckConfig; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const VERSION = '5.1.3' + const NAME = 'collapse' + const DATA_KEY = 'bs.collapse' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' + + const Default = { + toggle: true, + parent: null + } + + const DefaultType = { + toggle: 'boolean', + parent: '(null|element)' + } + + const EVENT_SHOW = `show${EVENT_KEY}` + const EVENT_SHOWN = `shown${EVENT_KEY}` + const EVENT_HIDE = `hide${EVENT_KEY}` + const EVENT_HIDDEN = `hidden${EVENT_KEY}` + const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + + const CLASS_NAME_SHOW = 'show' + const CLASS_NAME_COLLAPSE = 'collapse' + const CLASS_NAME_COLLAPSING = 'collapsing' + const CLASS_NAME_COLLAPSED = 'collapsed' + const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}` + const CLASS_NAME_HORIZONTAL = 'collapse-horizontal' + + const WIDTH = 'width' + const HEIGHT = 'height' + + const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing' + const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"]' + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Collapse = function(element, config) { + element = getElement(element) + + if (!element) { + return + } + + this._element = element + Data.set(this._element, DATA_KEY, this) + + this._isTransitioning = false + this._config = this._getConfig(config) + this._triggerArray = [] + + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) + + for (let i = 0, len = toggleList.length; i < len; i++) { + const elem = toggleList[i] + const selector = getSelectorFromElement(elem) + const filterElement = SelectorEngine.find(selector) + .filter(foundElem => foundElem === this._element) + + if (selector !== null && filterElement.length) { + this._selector = selector + this._triggerArray.push(elem) + } + } + + this._initializeChildren() + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()) + } + + if (this._config.toggle) { + this.toggle() + } + } + + // Getters + + Collapse.VERSION = VERSION; + + Collapse.Default = Default; + + Collapse.NAME = NAME; + + Collapse.DATA_KEY = 'bs.' + Collapse.NAME; + + Collapse.EVENT_KEY = '.' + Collapse.DATA_KEY; + + // Public + + Collapse.prototype.dispose = function() { + Data.remove(this._element, this.constructor.DATA_KEY) + EventHandler.off(this._element, this.constructor.EVENT_KEY) + + Object.getOwnPropertyNames(this).forEach(propertyName => { + this[propertyName] = null + }) + } + + Collapse.prototype._queueCallback = function(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated) + } + + Collapse.prototype.toggle = function() { + if (this._isShown()) { + this.hide() + } else { + this.show() + } + } + + Collapse.prototype.show = function() { + if (this._isTransitioning || this._isShown()) { + return + } + + let actives = [] + let activesData + + if (this._config.parent) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) + actives = SelectorEngine.find(SELECTOR_ACTIVES, this._config.parent).filter(elem => !children.includes(elem)) // remove children if greater depth + } + + const container = SelectorEngine.findOne(this._selector) + if (actives.length) { + const tempActiveData = actives.find(elem => container !== elem) + activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null + + if (activesData && activesData._isTransitioning) { + return + } + } + + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW) + if (startEvent.defaultPrevented) { + return + } + + actives.forEach(elemActive => { + if (container !== elemActive) { + Collapse.getOrCreateInstance(elemActive, {toggle: false}).hide() + } + + if (!activesData) { + Data.set(elemActive, DATA_KEY, null) + } + }) + + const dimension = this._getDimension() + + this._element.classList.remove(CLASS_NAME_COLLAPSE) + this._element.classList.add(CLASS_NAME_COLLAPSING) + + this._element.style[dimension] = 0 + + this._addAriaAndCollapsedClass(this._triggerArray, true) + this._isTransitioning = true + + const complete = () => { + this._isTransitioning = false + + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) + + this._element.style[dimension] = '' + + EventHandler.trigger(this._element, EVENT_SHOWN) + } + + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) + const scrollSize = `scroll${capitalizedDimension}` + + this._queueCallback(complete, this._element, true) + this._element.style[dimension] = `${this._element[scrollSize]}px` + } + + Collapse.prototype.hide = function() { + if (this._isTransitioning || !this._isShown()) { + return + } + + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE) + if (startEvent.defaultPrevented) { + return + } + + const dimension = this._getDimension() + + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px` + + reflow(this._element) + + this._element.classList.add(CLASS_NAME_COLLAPSING) + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) + + const triggerArrayLength = this._triggerArray.length + for (let i = 0; i < triggerArrayLength; i++) { + const trigger = this._triggerArray[i] + const elem = getElementFromSelector(trigger) + + if (elem && !this._isShown(elem)) { + this._addAriaAndCollapsedClass([trigger], false) + } + } + + this._isTransitioning = true + + const complete = () => { + this._isTransitioning = false + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE) + EventHandler.trigger(this._element, EVENT_HIDDEN) + } + + this._element.style[dimension] = '' + + this._queueCallback(complete, this._element, true) + } + + Collapse.prototype._isShown = function(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) + } + + // Private + + Collapse.prototype._getConfig = function(config) { + config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...config + } + config.toggle = Boolean(config.toggle) // Coerce string values + config.parent = getElement(config.parent) + typeCheckConfig(NAME, config, DefaultType) + return config + } + + Collapse.prototype._getDimension = function() { + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT + } + + Collapse.prototype._initializeChildren = function() { + if (!this._config.parent) { + return + } + + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) + SelectorEngine.find(SELECTOR_DATA_TOGGLE, this._config.parent).filter(elem => !children.includes(elem)) + .forEach(element => { + const selected = getElementFromSelector(element) + + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)) + } + }) + } + + Collapse.prototype._addAriaAndCollapsedClass = function(triggerArray, isOpen) { + if (!triggerArray.length) { + return + } + + triggerArray.forEach(elem => { + if (isOpen) { + elem.classList.remove(CLASS_NAME_COLLAPSED) + } else { + elem.classList.add(CLASS_NAME_COLLAPSED) + } + + elem.setAttribute('aria-expanded', isOpen) + }) + } + + // Static + + Collapse.getInstance = function(element) { + return Data.get(getElement(element), this.DATA_KEY) + } + + Collapse.getOrCreateInstance = function(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null) + } + + Collapse.jQueryInterface = function(config) { + return this.each(function () { + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false + } + + const data = Collapse.getOrCreateInstance(this, _config) + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + } + }) + } + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + // preventDefault only for <a> elements (which change the URL) not inside the collapsible element + if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) { + event.preventDefault() + } + + const selector = getSelectorFromElement(this) + const selectorElements = SelectorEngine.find(selector) + + selectorElements.forEach(element => { + Collapse.getOrCreateInstance(element, {toggle: false}).toggle() + }) + }) + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + * add .Collapse to jQuery only if jQuery is present + */ + + defineJQueryPlugin(Collapse) + + return Collapse; +}); diff --git a/lib/web/jquery/bootstrap/dom/data.js b/lib/web/jquery/bootstrap/dom/data.js new file mode 100644 index 0000000000000..9381207723aec --- /dev/null +++ b/lib/web/jquery/bootstrap/dom/data.js @@ -0,0 +1,61 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/data.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([], function() { + 'use strict'; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const elementMap = new Map() + + return { + set: function (element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()) + } + + const instanceMap = elementMap.get(element) + + // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + if (!instanceMap.has(key) && instanceMap.size !== 0) { + // eslint-disable-next-line no-console + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`) + return + } + + instanceMap.set(key, instance) + }, + + get: function (element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null + } + + return null + }, + + remove: function (element, key) { + if (!elementMap.has(element)) { + return + } + + const instanceMap = elementMap.get(element) + + instanceMap.delete(key) + + // free up element references if there are no instances left for an element + if (instanceMap.size === 0) { + elementMap.delete(element) + } + } + } +}); diff --git a/lib/web/jquery/bootstrap/dom/event-handler.js b/lib/web/jquery/bootstrap/dom/event-handler.js new file mode 100644 index 0000000000000..b5cabb75046cb --- /dev/null +++ b/lib/web/jquery/bootstrap/dom/event-handler.js @@ -0,0 +1,352 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/event-handler.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([ + "../util/index" +], function(Util) { + 'use strict'; + + const getjQuery = Util.getjQuery; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const namespaceRegex = /[^.]*(?=\..*)\.|.*/ + const stripNameRegex = /\..*/ + const stripUidRegex = /::\d+$/ + const eventRegistry = {} // Events storage + let uidEvent = 1 + const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + } + const customEventsRegex = /^(mouseenter|mouseleave)/i + const nativeEvents = new Set([ + 'click', + 'dblclick', + 'mouseup', + 'mousedown', + 'contextmenu', + 'mousewheel', + 'DOMMouseScroll', + 'mouseover', + 'mouseout', + 'mousemove', + 'selectstart', + 'selectend', + 'keydown', + 'keypress', + 'keyup', + 'orientationchange', + 'touchstart', + 'touchmove', + 'touchend', + 'touchcancel', + 'pointerdown', + 'pointermove', + 'pointerup', + 'pointerleave', + 'pointercancel', + 'gesturestart', + 'gesturechange', + 'gestureend', + 'focus', + 'blur', + 'change', + 'reset', + 'select', + 'submit', + 'focusin', + 'focusout', + 'load', + 'unload', + 'beforeunload', + 'resize', + 'move', + 'DOMContentLoaded', + 'readystatechange', + 'error', + 'abort', + 'scroll' + ]) + + /** + * ------------------------------------------------------------------------ + * Private methods + * ------------------------------------------------------------------------ + */ + + function getUidEvent(element, uid) { + return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++ + } + + function getEvent(element) { + const uid = getUidEvent(element) + + element.uidEvent = uid + eventRegistry[uid] = eventRegistry[uid] || {} + + return eventRegistry[uid] + } + + function bootstrapHandler(element, fn) { + return function handler(event) { + event.delegateTarget = element + + if (handler.oneOff) { + EventHandler.off(element, event.type, fn) + } + + return fn.apply(element, [event]) + } + } + + function bootstrapDelegationHandler(element, selector, fn) { + return function handler(event) { + const domElements = element.querySelectorAll(selector) + + for (let {target} = event; target && target !== this; target = target.parentNode) { + for (let i = domElements.length; i--;) { + if (domElements[i] === target) { + event.delegateTarget = target + + if (handler.oneOff) { + EventHandler.off(element, event.type, selector, fn) + } + + return fn.apply(target, [event]) + } + } + } + + // To please ESLint + return null + } + } + + function findHandler(events, handler, delegationSelector = null) { + const uidEventList = Object.keys(events) + + for (let i = 0, len = uidEventList.length; i < len; i++) { + const event = events[uidEventList[i]] + + if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { + return event + } + } + + return null + } + + function normalizeParams(originalTypeEvent, handler, delegationFn) { + const delegation = typeof handler === 'string' + const originalHandler = delegation ? delegationFn : handler + + let typeEvent = getTypeEvent(originalTypeEvent) + const isNative = nativeEvents.has(typeEvent) + + if (!isNative) { + typeEvent = originalTypeEvent + } + + return [delegation, originalHandler, typeEvent] + } + + function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { + if (typeof originalTypeEvent !== 'string' || !element) { + return + } + + if (!handler) { + handler = delegationFn + delegationFn = null + } + + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (customEventsRegex.test(originalTypeEvent)) { + const wrapFn = fn => { + return function (event) { + if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { + return fn.call(this, event) + } + } + } + + if (delegationFn) { + delegationFn = wrapFn(delegationFn) + } else { + handler = wrapFn(handler) + } + } + + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) + const events = getEvent(element) + const handlers = events[typeEvent] || (events[typeEvent] = {}) + const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null) + + if (previousFn) { + previousFn.oneOff = previousFn.oneOff && oneOff + + return + } + + const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) + const fn = delegation ? + bootstrapDelegationHandler(element, handler, delegationFn) : + bootstrapHandler(element, handler) + + fn.delegationSelector = delegation ? handler : null + fn.originalHandler = originalHandler + fn.oneOff = oneOff + fn.uidEvent = uid + handlers[uid] = fn + + element.addEventListener(typeEvent, fn, delegation) + } + + function removeHandler(element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector) + + if (!fn) { + return + } + + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)) + delete events[typeEvent][fn.uidEvent] + } + + function removeNamespacedHandlers(element, events, typeEvent, namespace) { + const storeElementEvent = events[typeEvent] || {} + + Object.keys(storeElementEvent).forEach(handlerKey => { + if (handlerKey.includes(namespace)) { + const event = storeElementEvent[handlerKey] + + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) + } + }) + } + + function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, '') + return customEvents[event] || event + } + + return { + on: function(element, event, handler, delegationFn) { + addHandler(element, event, handler, delegationFn, false) + }, + + one: function(element, event, handler, delegationFn) { + addHandler(element, event, handler, delegationFn, true) + }, + + off: function(element, originalTypeEvent, handler, delegationFn) { + if (typeof originalTypeEvent !== 'string' || !element) { + return + } + + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) + const inNamespace = typeEvent !== originalTypeEvent + const events = getEvent(element) + const isNamespace = originalTypeEvent.startsWith('.') + + if (typeof originalHandler !== 'undefined') { + // Simplest case: handler is passed, remove that listener ONLY. + if (!events || !events[typeEvent]) { + return + } + + removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null) + return + } + + if (isNamespace) { + Object.keys(events).forEach(elementEvent => { + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)) + }) + } + + const storeElementEvent = events[typeEvent] || {} + Object.keys(storeElementEvent).forEach(keyHandlers => { + const handlerKey = keyHandlers.replace(stripUidRegex, '') + + if (!inNamespace || originalTypeEvent.includes(handlerKey)) { + const event = storeElementEvent[keyHandlers] + + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) + } + }) + }, + + trigger: function(element, event, args) { + if (typeof event !== 'string' || !element) { + return null + } + + const $ = getjQuery() + const typeEvent = getTypeEvent(event) + const inNamespace = event !== typeEvent + const isNative = nativeEvents.has(typeEvent) + + let jQueryEvent + let bubbles = true + let nativeDispatch = true + let defaultPrevented = false + let evt = null + + if (inNamespace && $) { + jQueryEvent = $.Event(event, args) + + $(element).trigger(jQueryEvent) + bubbles = !jQueryEvent.isPropagationStopped() + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() + defaultPrevented = jQueryEvent.isDefaultPrevented() + } + + if (isNative) { + evt = document.createEvent('HTMLEvents') + evt.initEvent(typeEvent, bubbles, true) + } else { + evt = new CustomEvent(event, { + bubbles, + cancelable: true + }) + } + + // merge custom information in our event + if (typeof args !== 'undefined') { + Object.keys(args).forEach(key => { + Object.defineProperty(evt, key, { + get() { + return args[key] + } + }) + }) + } + + if (defaultPrevented) { + evt.preventDefault() + } + + if (nativeDispatch) { + element.dispatchEvent(evt) + } + + if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { + jQueryEvent.preventDefault() + } + + return evt + } + } +}); diff --git a/lib/web/jquery/bootstrap/dom/manipulator.js b/lib/web/jquery/bootstrap/dom/manipulator.js new file mode 100644 index 0000000000000..f9a563833bf19 --- /dev/null +++ b/lib/web/jquery/bootstrap/dom/manipulator.js @@ -0,0 +1,82 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([], function() { + 'use strict'; + + function normalizeData(val) { + if (val === 'true') { + return true + } + + if (val === 'false') { + return false + } + + if (val === Number(val).toString()) { + return Number(val) + } + + if (val === '' || val === 'null') { + return null + } + + return val + } + + function normalizeDataKey(key) { + return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`) + } + + return { + setDataAttribute: function(element, key, value) { + element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value) + }, + + removeDataAttribute: function(element, key) { + element.removeAttribute(`data-bs-${normalizeDataKey(key)}`) + }, + + getDataAttributes: function(element) { + if (!element) { + return {} + } + + const attributes = {} + + Object.keys(element.dataset) + .filter(key => key.startsWith('bs')) + .forEach(key => { + let pureKey = key.replace(/^bs/, '') + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length) + attributes[pureKey] = normalizeData(element.dataset[key]) + }) + + return attributes + }, + + getDataAttribute: function(element, key) { + return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)) + }, + + offset: function(element) { + const rect = element.getBoundingClientRect() + + return { + top: rect.top + window.pageYOffset, + left: rect.left + window.pageXOffset + } + }, + + position: function(element) { + return { + top: element.offsetTop, + left: element.offsetLeft + } + } + } +}); diff --git a/lib/web/jquery/bootstrap/dom/selector-engine.js b/lib/web/jquery/bootstrap/dom/selector-engine.js new file mode 100644 index 0000000000000..288f0d2d6d001 --- /dev/null +++ b/lib/web/jquery/bootstrap/dom/selector-engine.js @@ -0,0 +1,97 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/selector-engine.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([ + "../util/index" +], function(Util) { + 'use strict'; + + const isDisabled = Util.isDisabled; + const isVisible = Util.isVisible; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NODE_TEXT = 3 + + return { + find: function(selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)) + }, + + findOne: function(selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector) + }, + + children: function(element, selector) { + return [].concat(...element.children) + .filter(child => child.matches(selector)) + }, + + parents: function(element, selector) { + const parents = [] + + let ancestor = element.parentNode + + while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT) { + if (ancestor.matches(selector)) { + parents.push(ancestor) + } + + ancestor = ancestor.parentNode + } + + return parents + }, + + prev: function(element, selector) { + let previous = element.previousElementSibling + + while (previous) { + if (previous.matches(selector)) { + return [previous] + } + + previous = previous.previousElementSibling + } + + return [] + }, + + next: function(element, selector) { + let next = element.nextElementSibling + + while (next) { + if (next.matches(selector)) { + return [next] + } + + next = next.nextElementSibling + } + + return [] + }, + + focusableChildren: function(element) { + const focusables = [ + 'a', + 'button', + 'input', + 'textarea', + 'select', + 'details', + '[tabindex]', + '[contenteditable="true"]' + ].map(selector => `${selector}:not([tabindex^="-"])`).join(', ') + + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) + } + } +}); diff --git a/lib/web/jquery/bootstrap/tab.js b/lib/web/jquery/bootstrap/tab.js new file mode 100644 index 0000000000000..928f8de3f376b --- /dev/null +++ b/lib/web/jquery/bootstrap/tab.js @@ -0,0 +1,264 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): tab.js and base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +define([ + "./util/index", + "./dom/event-handler", + "./dom/selector-engine" +], function(Util, EventHandler, SelectorEngine) { + 'use strict'; + + const defineJQueryPlugin = Util.defineJQueryPlugin; + const executeAfterTransition = Util.executeAfterTransition; + const getElement = Util.getElement; + const getElementFromSelector = Util.getElementFromSelector; + const isDisabled = Util.isDisabled; + const reflow = Util.reflow; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const VERSION = '5.1.3' + const NAME = 'tab' + const DATA_KEY = 'bs.tab' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' + + const EVENT_HIDE = `hide${EVENT_KEY}` + const EVENT_HIDDEN = `hidden${EVENT_KEY}` + const EVENT_SHOW = `show${EVENT_KEY}` + const EVENT_SHOWN = `shown${EVENT_KEY}` + const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + + const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu' + const CLASS_NAME_ACTIVE = 'active' + const CLASS_NAME_FADE = 'fade' + const CLASS_NAME_SHOW = 'show' + + const SELECTOR_DROPDOWN = '.dropdown' + const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' + const SELECTOR_ACTIVE = '.active' + const SELECTOR_ACTIVE_UL = ':scope > li > .active' + const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' + const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' + const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active' + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + function Tab(element) { + element = getElement(element) + + if (!element) { + return + } + + this._element = element + Data.set(this._element, DATA_KEY, this) + } + + // Getters + + Tab.VERSION = VERSION; + + Tab.NAME = NAME; + + Tab.DATA_KEY = 'bs.' + Tab.NAME; + + Tab.EVENT_KEY = '.' + Tab.DATA_KEY; + + // Public + + Tab.prototype.dispose = function() { + Data.remove(this._element, this.constructor.DATA_KEY) + EventHandler.off(this._element, this.constructor.EVENT_KEY) + + Object.getOwnPropertyNames(this).forEach(propertyName => { + this[propertyName] = null + }) + } + + Tab.prototype._queueCallback = function(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated) + } + + Tab.prototype.show = function() { + if ((this._element.parentNode && + this._element.parentNode.nodeType === Node.ELEMENT_NODE && + this._element.classList.contains(CLASS_NAME_ACTIVE))) { + return + } + + let previous + const target = getElementFromSelector(this._element) + const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP) + + if (listElement) { + const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE + previous = SelectorEngine.find(itemSelector, listElement) + previous = previous[previous.length - 1] + } + + const hideEvent = previous ? + EventHandler.trigger(previous, EVENT_HIDE, { + relatedTarget: this._element + }) : + null + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { + relatedTarget: previous + }) + + if (showEvent.defaultPrevented || (hideEvent !== null && hideEvent.defaultPrevented)) { + return + } + + this._activate(this._element, listElement) + + const complete = () => { + EventHandler.trigger(previous, EVENT_HIDDEN, { + relatedTarget: this._element + }) + EventHandler.trigger(this._element, EVENT_SHOWN, { + relatedTarget: previous + }) + } + + if (target) { + this._activate(target, target.parentNode, complete) + } else { + complete() + } + } + + // Private + + Tab.prototype._activate = function(element, container, callback) { + const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? + SelectorEngine.find(SELECTOR_ACTIVE_UL, container) : + SelectorEngine.children(container, SELECTOR_ACTIVE) + + const active = activeElements[0] + const isTransitioning = callback && (active && active.classList.contains(CLASS_NAME_FADE)) + + const complete = () => this._transitionComplete(element, active, callback) + + if (active && isTransitioning) { + active.classList.remove(CLASS_NAME_SHOW) + this._queueCallback(complete, element, true) + } else { + complete() + } + } + + Tab.prototype._transitionComplete = function(element, active, callback) { + if (active) { + active.classList.remove(CLASS_NAME_ACTIVE) + + const dropdownChild = SelectorEngine.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD, active.parentNode) + + if (dropdownChild) { + dropdownChild.classList.remove(CLASS_NAME_ACTIVE) + } + + if (active.getAttribute('role') === 'tab') { + active.setAttribute('aria-selected', false) + } + } + + element.classList.add(CLASS_NAME_ACTIVE) + if (element.getAttribute('role') === 'tab') { + element.setAttribute('aria-selected', true) + } + + reflow(element) + + if (element.classList.contains(CLASS_NAME_FADE)) { + element.classList.add(CLASS_NAME_SHOW) + } + + let parent = element.parentNode + if (parent && parent.nodeName === 'LI') { + parent = parent.parentNode + } + + if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) { + const dropdownElement = element.closest(SELECTOR_DROPDOWN) + + if (dropdownElement) { + SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement) + .forEach(dropdown => dropdown.classList.add(CLASS_NAME_ACTIVE)) + } + + element.setAttribute('aria-expanded', true) + } + + if (callback) { + callback() + } + } + + // Static + + Tab.getInstance = function(element) { + return Data.get(getElement(element), this.DATA_KEY) + } + + Tab.getOrCreateInstance = function(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null) + } + + Tab.jQueryInterface = function(config) { + return this.each(function () { + const data = Tab.getOrCreateInstance(this) + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + } + }) + } + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + const data = Tab.getOrCreateInstance(this) + data.show() + }) + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + * add .Tab to jQuery only if jQuery is present + */ + + defineJQueryPlugin(Tab) + + return Tab; +}); diff --git a/lib/web/jquery/bootstrap/util/index.js b/lib/web/jquery/bootstrap/util/index.js new file mode 100644 index 0000000000000..f4e8980a61838 --- /dev/null +++ b/lib/web/jquery/bootstrap/util/index.js @@ -0,0 +1,341 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): util/index.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + +define([ + "jquery", + 'domReady!' +], function() { + 'use strict'; + + const MAX_UID = 1000000 + const MILLISECONDS_MULTIPLIER = 1000 + const TRANSITION_END = 'transitionend' + + // Shoutout AngusCroll (https://goo.gl/pxwQGp) + const toType = obj => { + if (obj === null || obj === undefined) { + return `${obj}` + } + + return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() + } + + /** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ + + const getUID = prefix => { + do { + prefix += Math.floor(Math.random() * MAX_UID) + } while (document.getElementById(prefix)) + + return prefix + } + + const getSelector = element => { + let selector = element.getAttribute('data-bs-target') + + if (!selector || selector === '#') { + let hrefAttr = element.getAttribute('href') + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) { + return null + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { + hrefAttr = `#${hrefAttr.split('#')[1]}` + } + + selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null + } + + return selector + } + + const getSelectorFromElement = element => { + const selector = getSelector(element) + + if (selector) { + return document.querySelector(selector) ? selector : null + } + + return null + } + + const getElementFromSelector = element => { + const selector = getSelector(element) + + return selector ? document.querySelector(selector) : null + } + + const getTransitionDurationFromElement = element => { + if (!element) { + return 0 + } + + // Get transition-duration of the element + let {transitionDuration, transitionDelay} = window.getComputedStyle(element) + + const floatTransitionDuration = Number.parseFloat(transitionDuration) + const floatTransitionDelay = Number.parseFloat(transitionDelay) + + // Return 0 if element or transition duration is not found + if (!floatTransitionDuration && !floatTransitionDelay) { + return 0 + } + + // If multiple durations are defined, take the first + transitionDuration = transitionDuration.split(',')[0] + transitionDelay = transitionDelay.split(',')[0] + + return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER + } + + const triggerTransitionEnd = element => { + element.dispatchEvent(new Event(TRANSITION_END)) + } + + const isElement = obj => { + if (!obj || typeof obj !== 'object') { + return false + } + + if (typeof obj.jquery !== 'undefined') { + obj = obj[0] + } + + return typeof obj.nodeType !== 'undefined' + } + + const getElement = obj => { + if (isElement(obj)) { // it's a jQuery object or a node element + return obj.jquery ? obj[0] : obj + } + + if (typeof obj === 'string' && obj.length > 0) { + return document.querySelector(obj) + } + + return null + } + + const typeCheckConfig = (componentName, config, configTypes) => { + Object.keys(configTypes).forEach(property => { + const expectedTypes = configTypes[property] + const value = config[property] + const valueType = value && isElement(value) ? 'element' : toType(value) + + if (!new RegExp(expectedTypes).test(valueType)) { + throw new TypeError( + `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` + ) + } + }) + } + + const isVisible = element => { + if (!isElement(element) || element.getClientRects().length === 0) { + return false + } + + return getComputedStyle(element).getPropertyValue('visibility') === 'visible' + } + + const isDisabled = element => { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return true + } + + if (element.classList.contains('disabled')) { + return true + } + + if (typeof element.disabled !== 'undefined') { + return element.disabled + } + + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false' + } + + const findShadowRoot = element => { + if (!document.documentElement.attachShadow) { + return null + } + + // Can find the shadow root otherwise it'll return the document + if (typeof element.getRootNode === 'function') { + const root = element.getRootNode() + return root instanceof ShadowRoot ? root : null + } + + if (element instanceof ShadowRoot) { + return element + } + + // when we don't find a shadow root + if (!element.parentNode) { + return null + } + + return findShadowRoot(element.parentNode) + } + + const noop = () => {} + + /** + * Trick to restart an element's animation + * + * @param {HTMLElement} element + * @return void + * + * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + */ + const reflow = element => { + // eslint-disable-next-line no-unused-expressions + element.offsetHeight + } + + const getjQuery = () => { + const {jQuery} = window + + if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return jQuery + } + + return null + } + + const DOMContentLoadedCallbacks = [] + + const onDOMContentLoaded = callback => { + if (document.readyState === 'loading') { + // add listener on the first call when the document is in loading state + if (!DOMContentLoadedCallbacks.length) { + document.addEventListener('DOMContentLoaded', () => { + DOMContentLoadedCallbacks.forEach(callback => callback()) + }) + } + + DOMContentLoadedCallbacks.push(callback) + } else { + callback() + } + } + + const isRTL = () => document.documentElement.dir === 'rtl' + + const defineJQueryPlugin = plugin => { + onDOMContentLoaded(() => { + const $ = getjQuery() + /* istanbul ignore if */ + if ($) { + const name = plugin.NAME + const JQUERY_NO_CONFLICT = $.fn[name] + $.fn[name] = plugin.jQueryInterface + $.fn[name].Constructor = plugin + $.fn[name].noConflict = () => { + $.fn[name] = JQUERY_NO_CONFLICT + return plugin.jQueryInterface + } + } + }) + } + + const execute = callback => { + if (typeof callback === 'function') { + callback() + } + } + + const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback) + return + } + + const durationPadding = 5 + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding + + let called = false + + const handler = ({ target }) => { + if (target !== transitionElement) { + return + } + + called = true + transitionElement.removeEventListener(TRANSITION_END, handler) + execute(callback) + } + + transitionElement.addEventListener(TRANSITION_END, handler) + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement) + } + }, emulatedDuration) + } + + /** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ + const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + let index = list.indexOf(activeElement) + + // if the element does not exist in the list return an element depending on the direction and if cycle is allowed + if (index === -1) { + return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0] + } + + const listLength = list.length + + index += shouldGetNext ? 1 : -1 + + if (isCycleAllowed) { + index = (index + listLength) % listLength + } + + return list[Math.max(0, Math.min(index, listLength - 1))] + } + + return { + getElement, + getUID, + getSelectorFromElement, + getElementFromSelector, + getTransitionDurationFromElement, + triggerTransitionEnd, + isElement, + typeCheckConfig, + isVisible, + isDisabled, + findShadowRoot, + noop, + getNextActiveElement, + reflow, + getjQuery, + onDOMContentLoaded, + isRTL, + defineJQueryPlugin, + execute, + executeAfterTransition + }; +}); diff --git a/lib/web/jquery/jquery-ui-timepicker-addon.js b/lib/web/jquery/jquery-ui-timepicker-addon.js index 0a52b7dec4f19..1e833982a94b3 100644 --- a/lib/web/jquery/jquery-ui-timepicker-addon.js +++ b/lib/web/jquery/jquery-ui-timepicker-addon.js @@ -1,2154 +1,2291 @@ -/*! jQuery Timepicker Addon - v1.4.3 - 2013-11-30 +/*! jQuery Timepicker Addon - v1.6.3 - 2016-04-20 * http://trentrichardson.com/examples/timepicker -* Copyright (c) 2013 Trent Richardson; Licensed MIT */ +* Copyright (c) 2016 Trent Richardson; Licensed MIT */ (function (factory) { if (typeof define === 'function' && define.amd) { - define([ - "jquery", - "jquery/ui" - ], factory); + define(['jquery', 'jquery/ui'], factory); } else { factory(jQuery); } }(function ($) { - /* - * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" - */ - $.ui.timepicker = $.ui.timepicker || {}; - if ($.ui.timepicker.version) { - return; - } - - /* - * Extend jQueryUI, get it started with our version number - */ - $.extend($.ui, { - timepicker: { - version: "1.4.3" - } - }); - - /* - * Timepicker manager. - * Use the singleton instance of this class, $.timepicker, to interact with the time picker. - * Settings for (groups of) time pickers are maintained in an instance object, - * allowing multiple different settings on the same page. - */ - var Timepicker = function () { - this.regional = []; // Available regional settings, indexed by language code - this.regional[''] = { // Default regional settings - currentText: 'Now', - closeText: 'Done', - amNames: ['AM', 'A'], - pmNames: ['PM', 'P'], - timeFormat: 'HH:mm', - timeSuffix: '', - timeOnlyTitle: 'Choose Time', - timeText: 'Time', - hourText: 'Hour', - minuteText: 'Minute', - secondText: 'Second', - millisecText: 'Millisecond', - microsecText: 'Microsecond', - timezoneText: 'Time Zone', - isRTL: false - }; - this._defaults = { // Global defaults for all the datetime picker instances - showButtonPanel: true, - timeOnly: false, - showHour: null, - showMinute: null, - showSecond: null, - showMillisec: null, - showMicrosec: null, - showTimezone: null, - showTime: true, - stepHour: 1, - stepMinute: 1, - stepSecond: 1, - stepMillisec: 1, - stepMicrosec: 1, - hour: 0, - minute: 0, - second: 0, - millisec: 0, - microsec: 0, - timezone: null, - hourMin: 0, - minuteMin: 0, - secondMin: 0, - millisecMin: 0, - microsecMin: 0, - hourMax: 23, - minuteMax: 59, - secondMax: 59, - millisecMax: 999, - microsecMax: 999, - minDateTime: null, - maxDateTime: null, - onSelect: null, - hourGrid: 0, - minuteGrid: 0, - secondGrid: 0, - millisecGrid: 0, - microsecGrid: 0, - alwaysSetTime: true, - separator: ' ', - altFieldTimeOnly: true, - altTimeFormat: null, - altSeparator: null, - altTimeSuffix: null, - pickerTimeFormat: null, - pickerTimeSuffix: null, - showTimepicker: true, - timezoneList: null, - addSliderAccess: false, - sliderAccessArgs: null, - controlType: 'slider', - defaultValue: null, - parse: 'strict' - }; - $.extend(this._defaults, this.regional['']); - }; - - $.extend(Timepicker.prototype, { - $input: null, - $altInput: null, - $timeObj: null, - inst: null, - hour_slider: null, - minute_slider: null, - second_slider: null, - millisec_slider: null, - microsec_slider: null, - timezone_select: null, - hour: 0, - minute: 0, - second: 0, - millisec: 0, - microsec: 0, - timezone: null, - hourMinOriginal: null, - minuteMinOriginal: null, - secondMinOriginal: null, - millisecMinOriginal: null, - microsecMinOriginal: null, - hourMaxOriginal: null, - minuteMaxOriginal: null, - secondMaxOriginal: null, - millisecMaxOriginal: null, - microsecMaxOriginal: null, - ampm: '', - formattedDate: '', - formattedTime: '', - formattedDateTime: '', - timezoneList: null, - units: ['hour', 'minute', 'second', 'millisec', 'microsec'], - support: {}, - control: null, - - /* - * Override the default settings for all instances of the time picker. - * @param {Object} settings object - the new settings to use as defaults (anonymous object) - * @return {Object} the manager object - */ - setDefaults: function (settings) { - extendRemove(this._defaults, settings || {}); - return this; - }, - - /* - * Create a new Timepicker instance - */ - _newInst: function ($input, opts) { - var tp_inst = new Timepicker(), - inlineSettings = {}, - fns = {}, - overrides, i; - - for (var attrName in this._defaults) { - if (this._defaults.hasOwnProperty(attrName)) { - var attrValue = $input.attr('time:' + attrName); - if (attrValue) { - try { - inlineSettings[attrName] = eval(attrValue); - } catch (err) { - inlineSettings[attrName] = attrValue; - } - } - } - } - - overrides = { - beforeShow: function (input, dp_inst) { - if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { - return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst); - } - }, - onChangeMonthYear: function (year, month, dp_inst) { - // Update the time as well : this prevents the time from disappearing from the $input field. - tp_inst._updateDateTime(dp_inst); - if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { - tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); - } - }, - onClose: function (dateText, dp_inst) { - if (tp_inst.timeDefined === true && $input.val() !== '') { - tp_inst._updateDateTime(dp_inst); - } - if ($.isFunction(tp_inst._defaults.evnts.onClose)) { - tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); - } - } - }; - for (i in overrides) { - if (overrides.hasOwnProperty(i)) { - fns[i] = opts[i] || null; - } - } - - tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, { - evnts: fns, - timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); - }); - tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { - return val.toUpperCase(); - }); - tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { - return val.toUpperCase(); - }); - - // detect which units are supported - tp_inst.support = detectSupport( - tp_inst._defaults.timeFormat + - (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') + - (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); - - // controlType is string - key to our this._controls - if (typeof(tp_inst._defaults.controlType) === 'string') { - if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { - tp_inst._defaults.controlType = 'select'; - } - tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; - } - // controlType is an object and must implement create, options, value methods - else { - tp_inst.control = tp_inst._defaults.controlType; - } - - // prep the timezone options - var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60, - 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840]; - if (tp_inst._defaults.timezoneList !== null) { - timezoneList = tp_inst._defaults.timezoneList; - } - var tzl = timezoneList.length, tzi = 0, tzv = null; - if (tzl > 0 && typeof timezoneList[0] !== 'object') { - for (; tzi < tzl; tzi++) { - tzv = timezoneList[tzi]; - timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; - } - } - tp_inst._defaults.timezoneList = timezoneList; - - // set the default units - tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) : - ((new Date()).getTimezoneOffset() * -1); - tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin : - tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour; - tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin : - tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; - tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin : - tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second; - tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin : - tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec; - tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin : - tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec; - tp_inst.ampm = ''; - tp_inst.$input = $input; - - if (tp_inst._defaults.altField) { - tp_inst.$altInput = $(tp_inst._defaults.altField).css({ - cursor: 'pointer' - }).focus(function () { - $input.trigger("focus"); - }); - } - - if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { - tp_inst._defaults.minDate = new Date(); - } - if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) { - tp_inst._defaults.maxDate = new Date(); - } - - // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. - if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { - tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); - } - if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { - tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); - } - if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { - tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); - } - if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { - tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); - } - tp_inst.$input.bind('focus', function () { - tp_inst._onFocus(); - }); - - return tp_inst; - }, - - /* - * add our sliders to the calendar - */ - _addTimePicker: function (dp_inst) { - var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val(); - - this.timeDefined = this._parseTime(currDT); - this._limitMinMaxDateTime(dp_inst, false); - this._injectTimePicker(); - }, - - /* - * parse the time string from input value or _setTime - */ - _parseTime: function (timeString, withDate) { - if (!this.inst) { - this.inst = $.datepicker._getInst(this.$input[0]); - } - - if (withDate || !this._defaults.timeOnly) { - var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); - try { - var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); - if (!parseRes.timeObj) { - return false; - } - $.extend(this, parseRes.timeObj); - } catch (err) { - $.timepicker.log("Error parsing the date/time string: " + err + - "\ndate/time string = " + timeString + - "\ntimeFormat = " + this._defaults.timeFormat + - "\ndateFormat = " + dp_dateFormat); - return false; - } - return true; - } else { - var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); - if (!timeObj) { - return false; - } - $.extend(this, timeObj); - return true; - } - }, - - /* - * generate and inject html for timepicker into ui datepicker - */ - _injectTimePicker: function () { - var $dp = this.inst.dpDiv, - o = this.inst.settings, - tp_inst = this, - litem = '', - uitem = '', - show = null, - max = {}, - gridSize = {}, - size = null, - i = 0, - l = 0; - - // Prevent displaying twice - if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { - var noDisplay = ' style="display:none;"', - html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + - '<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>'; - - // Create the markup - for (i = 0, l = this.units.length; i < l; i++) { - litem = this.units[i]; - uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); - show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; - - // Added by Peter Medeiros: - // - Figure out what the hour/minute/second max should be based on the step values. - // - Example: if stepMinute is 15, then minMax is 45. - max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); - gridSize[litem] = 0; - - html += '<dt class="ui_tpicker_' + litem + '_label"' + (show ? '' : noDisplay) + '>' + o[litem + 'Text'] + '</dt>' + - '<dd class="ui_tpicker_' + litem + '"><div class="ui_tpicker_' + litem + '_slider"' + (show ? '' : noDisplay) + '></div>'; - - if (show && o[litem + 'Grid'] > 0) { - html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; - - if (litem === 'hour') { - for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { - gridSize[litem]++; - var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); - html += '<td data-for="' + litem + '">' + tmph + '</td>'; - } - } - else { - for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { - gridSize[litem]++; - html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>'; - } - } - - html += '</tr></table></div>'; - } - html += '</dd>'; - } - - // Timezone - var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; - html += '<dt class="ui_tpicker_timezone_label"' + (showTz ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; - html += '<dd class="ui_tpicker_timezone" ' + (showTz ? '' : noDisplay) + '></dd>'; - - // Create the elements from string - html += '</dl></div>'; - var $tp = $(html); - - // if we only want time picker... - if (o.timeOnly === true) { - $tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>'); - $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); - } - - // add sliders, adjust grids, add events - for (i = 0, l = tp_inst.units.length; i < l; i++) { - litem = tp_inst.units[i]; - uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); - show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; - - // add the slider - tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); - - // adjust the grid and add click event - if (show && o[litem + 'Grid'] > 0) { - size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); - $tp.find('.ui_tpicker_' + litem + ' table').css({ - width: size + "%", - marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), - marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', - borderCollapse: 'collapse' - }).find("td").click(function (e) { - var $t = $(this), - h = $t.html(), - n = parseInt(h.replace(/[^0-9]/g), 10), - ap = h.replace(/[^apm]/ig), - f = $t.data('for'); // loses scope, so we use data-for - - if (f === 'hour') { - if (ap.indexOf('p') !== -1 && n < 12) { - n += 12; - } - else { - if (ap.indexOf('a') !== -1 && n === 12) { - n = 0; - } - } - } - - tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); - - tp_inst._onTimeChange(); - tp_inst._onSelectHandler(); - }).css({ - cursor: 'pointer', - width: (100 / gridSize[litem]) + '%', - textAlign: 'center', - overflow: 'hidden' - }); - } // end if grid > 0 - } // end for loop - - // Add timezone options - this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select"); - $.fn.append.apply(this.timezone_select, - $.map(o.timezoneList, function (val, idx) { - return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val); - })); - if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") { - var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1; - if (local_timezone === this.timezone) { - selectLocalTimezone(tp_inst); - } else { - this.timezone_select.val(this.timezone); - } - } else { - if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") { - this.timezone_select.val(o.timezone); - } else { - selectLocalTimezone(tp_inst); - } - } - this.timezone_select.change(function () { - tp_inst._onTimeChange(); - tp_inst._onSelectHandler(); - }); - // End timezone options - - // inject timepicker into datepicker - var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); - if ($buttonPanel.length) { - $buttonPanel.before($tp); - } else { - $dp.append($tp); - } - - this.$timeObj = $tp.find('.ui_tpicker_time'); - - if (this.inst !== null) { - var timeDefined = this.timeDefined; - this._onTimeChange(); - this.timeDefined = timeDefined; - } - - // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ - if (this._defaults.addSliderAccess) { - var sliderAccessArgs = this._defaults.sliderAccessArgs, - rtl = this._defaults.isRTL; - sliderAccessArgs.isRTL = rtl; - - setTimeout(function () { // fix for inline mode - if ($tp.find('.ui-slider-access').length === 0) { - $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); - - // fix any grids since sliders are shorter - var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); - if (sliderAccessWidth) { - $tp.find('table:visible').each(function () { - var $g = $(this), - oldWidth = $g.outerWidth(), - oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''), - newWidth = oldWidth - sliderAccessWidth, - newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%', - css = { width: newWidth, marginRight: 0, marginLeft: 0 }; - css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft; - $g.css(css); - }); - } - } - }, 10); - } - // end slideAccess integration - - tp_inst._limitMinMaxDateTime(this.inst, true); - } - }, - - /* - * This function tries to limit the ability to go outside the - * min/max date range - */ - _limitMinMaxDateTime: function (dp_inst, adjustSliders) { - var o = this._defaults, - dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); - - if (!this._defaults.showTimepicker) { - return; - } // No time so nothing to check here - - if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) { - var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), - minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); - - if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) { - this.hourMinOriginal = o.hourMin; - this.minuteMinOriginal = o.minuteMin; - this.secondMinOriginal = o.secondMin; - this.millisecMinOriginal = o.millisecMin; - this.microsecMinOriginal = o.microsecMin; - } - - if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() === dp_date.getTime()) { - this._defaults.hourMin = minDateTime.getHours(); - if (this.hour <= this._defaults.hourMin) { - this.hour = this._defaults.hourMin; - this._defaults.minuteMin = minDateTime.getMinutes(); - if (this.minute <= this._defaults.minuteMin) { - this.minute = this._defaults.minuteMin; - this._defaults.secondMin = minDateTime.getSeconds(); - if (this.second <= this._defaults.secondMin) { - this.second = this._defaults.secondMin; - this._defaults.millisecMin = minDateTime.getMilliseconds(); - if (this.millisec <= this._defaults.millisecMin) { - this.millisec = this._defaults.millisecMin; - this._defaults.microsecMin = minDateTime.getMicroseconds(); - } else { - if (this.microsec < this._defaults.microsecMin) { - this.microsec = this._defaults.microsecMin; - } - this._defaults.microsecMin = this.microsecMinOriginal; - } - } else { - this._defaults.millisecMin = this.millisecMinOriginal; - this._defaults.microsecMin = this.microsecMinOriginal; - } - } else { - this._defaults.secondMin = this.secondMinOriginal; - this._defaults.millisecMin = this.millisecMinOriginal; - this._defaults.microsecMin = this.microsecMinOriginal; - } - } else { - this._defaults.minuteMin = this.minuteMinOriginal; - this._defaults.secondMin = this.secondMinOriginal; - this._defaults.millisecMin = this.millisecMinOriginal; - this._defaults.microsecMin = this.microsecMinOriginal; - } - } else { - this._defaults.hourMin = this.hourMinOriginal; - this._defaults.minuteMin = this.minuteMinOriginal; - this._defaults.secondMin = this.secondMinOriginal; - this._defaults.millisecMin = this.millisecMinOriginal; - this._defaults.microsecMin = this.microsecMinOriginal; - } - } - - if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) { - var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), - maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); - - if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) { - this.hourMaxOriginal = o.hourMax; - this.minuteMaxOriginal = o.minuteMax; - this.secondMaxOriginal = o.secondMax; - this.millisecMaxOriginal = o.millisecMax; - this.microsecMaxOriginal = o.microsecMax; - } - - if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() === dp_date.getTime()) { - this._defaults.hourMax = maxDateTime.getHours(); - if (this.hour >= this._defaults.hourMax) { - this.hour = this._defaults.hourMax; - this._defaults.minuteMax = maxDateTime.getMinutes(); - if (this.minute >= this._defaults.minuteMax) { - this.minute = this._defaults.minuteMax; - this._defaults.secondMax = maxDateTime.getSeconds(); - if (this.second >= this._defaults.secondMax) { - this.second = this._defaults.secondMax; - this._defaults.millisecMax = maxDateTime.getMilliseconds(); - if (this.millisec >= this._defaults.millisecMax) { - this.millisec = this._defaults.millisecMax; - this._defaults.microsecMax = maxDateTime.getMicroseconds(); - } else { - if (this.microsec > this._defaults.microsecMax) { - this.microsec = this._defaults.microsecMax; - } - this._defaults.microsecMax = this.microsecMaxOriginal; - } - } else { - this._defaults.millisecMax = this.millisecMaxOriginal; - this._defaults.microsecMax = this.microsecMaxOriginal; - } - } else { - this._defaults.secondMax = this.secondMaxOriginal; - this._defaults.millisecMax = this.millisecMaxOriginal; - this._defaults.microsecMax = this.microsecMaxOriginal; - } - } else { - this._defaults.minuteMax = this.minuteMaxOriginal; - this._defaults.secondMax = this.secondMaxOriginal; - this._defaults.millisecMax = this.millisecMaxOriginal; - this._defaults.microsecMax = this.microsecMaxOriginal; - } - } else { - this._defaults.hourMax = this.hourMaxOriginal; - this._defaults.minuteMax = this.minuteMaxOriginal; - this._defaults.secondMax = this.secondMaxOriginal; - this._defaults.millisecMax = this.millisecMaxOriginal; - this._defaults.microsecMax = this.microsecMaxOriginal; - } - } - - if (adjustSliders !== undefined && adjustSliders === true) { - var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10), - minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10), - secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10), - millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10), - microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10); - - if (this.hour_slider) { - this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax }); - this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour)); - } - if (this.minute_slider) { - this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax }); - this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute)); - } - if (this.second_slider) { - this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax }); - this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond)); - } - if (this.millisec_slider) { - this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax }); - this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec)); - } - if (this.microsec_slider) { - this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax }); - this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec)); - } - } - - }, - - /* - * when a slider moves, set the internal time... - * on time change is also called when the time is updated in the text field - */ - _onTimeChange: function () { - if (!this._defaults.showTimepicker) { - return; - } - var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false, - minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false, - second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false, - millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false, - microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false, - timezone = (this.timezone_select) ? this.timezone_select.val() : false, - o = this._defaults, - pickerTimeFormat = o.pickerTimeFormat || o.timeFormat, - pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix; - - if (typeof(hour) === 'object') { - hour = false; - } - if (typeof(minute) === 'object') { - minute = false; - } - if (typeof(second) === 'object') { - second = false; - } - if (typeof(millisec) === 'object') { - millisec = false; - } - if (typeof(microsec) === 'object') { - microsec = false; - } - if (typeof(timezone) === 'object') { - timezone = false; - } - - if (hour !== false) { - hour = parseInt(hour, 10); - } - if (minute !== false) { - minute = parseInt(minute, 10); - } - if (second !== false) { - second = parseInt(second, 10); - } - if (millisec !== false) { - millisec = parseInt(millisec, 10); - } - if (microsec !== false) { - microsec = parseInt(microsec, 10); - } - if (timezone !== false) { - timezone = timezone.toString(); - } - - var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; - - // If the update was done in the input field, the input field should not be updated. - // If the update was done using the sliders, update the input field. - var hasChanged = ( - hour !== parseInt(this.hour,10) || // sliders should all be numeric - minute !== parseInt(this.minute,10) || - second !== parseInt(this.second,10) || - millisec !== parseInt(this.millisec,10) || - microsec !== parseInt(this.microsec,10) || - (this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || - (this.timezone !== null && timezone !== this.timezone.toString()) // could be numeric or "EST" format, so use toString() - ); - - if (hasChanged) { - - if (hour !== false) { - this.hour = hour; - } - if (minute !== false) { - this.minute = minute; - } - if (second !== false) { - this.second = second; - } - if (millisec !== false) { - this.millisec = millisec; - } - if (microsec !== false) { - this.microsec = microsec; - } - if (timezone !== false) { - this.timezone = timezone; - } - - if (!this.inst) { - this.inst = $.datepicker._getInst(this.$input[0]); - } - - this._limitMinMaxDateTime(this.inst, true); - } - if (this.support.ampm) { - this.ampm = ampm; - } - - // Updates the time within the timepicker - this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o); - if (this.$timeObj) { - if (pickerTimeFormat === o.timeFormat) { - this.$timeObj.text(this.formattedTime + pickerTimeSuffix); - } - else { - this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix); - } - } - - this.timeDefined = true; - if (hasChanged) { - this._updateDateTime(); - this.$input.focus(); - } - }, - - /* - * call custom onSelect. - * bind to sliders slidestop, and grid click. - */ - _onSelectHandler: function () { - var onSelect = this._defaults.onSelect || this.inst.settings.onSelect; - var inputEl = this.$input ? this.$input[0] : null; - if (onSelect && inputEl) { - onSelect.apply(inputEl, [this.formattedDateTime, this]); - } - }, - - /* - * update our input with the new date time.. - */ - _updateDateTime: function (dp_inst) { - dp_inst = this.inst || dp_inst; - var dtTmp = (dp_inst.currentYear > 0? - new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) : - new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), - dt = $.datepicker._daylightSavingAdjust(dtTmp), - //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), - //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)), - dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), - formatCfg = $.datepicker._getFormatConfig(dp_inst), - timeAvailable = dt !== null && this.timeDefined; - this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); - var formattedDateTime = this.formattedDate; - - // if a slider was changed but datepicker doesn't have a value yet, set it - if (dp_inst.lastVal === "") { + /* + * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" + */ + $.ui.timepicker = $.ui.timepicker || {}; + if ($.ui.timepicker.version) { + return; + } + + /* + * Extend jQueryUI, get it started with our version number + */ + $.extend($.ui, { + timepicker: { + version: "1.6.3" + } + }); + + /* + * Timepicker manager. + * Use the singleton instance of this class, $.timepicker, to interact with the time picker. + * Settings for (groups of) time pickers are maintained in an instance object, + * allowing multiple different settings on the same page. + */ + var Timepicker = function () { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'HH:mm', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + microsecText: 'Microsecond', + timezoneText: 'Time Zone', + isRTL: false + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + timeOnlyShowDate: false, + showHour: null, + showMinute: null, + showSecond: null, + showMillisec: null, + showMicrosec: null, + showTimezone: null, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + stepMicrosec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + microsecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + microsecMax: 999, + minDateTime: null, + maxDateTime: null, + maxTime: null, + minTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + microsecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + altTimeFormat: null, + altSeparator: null, + altTimeSuffix: null, + altRedirectFocus: true, + pickerTimeFormat: null, + pickerTimeSuffix: null, + showTimepicker: true, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null, + controlType: 'slider', + oneLine: false, + defaultValue: null, + parse: 'strict', + afterInject: null + }; + $.extend(this._defaults, this.regional['']); + }; + + $.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + microsec_slider: null, + timezone_select: null, + maxTime: null, + minTime: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + microsecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + microsecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + units: ['hour', 'minute', 'second', 'millisec', 'microsec'], + support: {}, + control: null, + + /* + * Override the default settings for all instances of the time picker. + * @param {Object} settings object - the new settings to use as defaults (anonymous object) + * @return {Object} the manager object + */ + setDefaults: function (settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* + * Create a new Timepicker instance + */ + _newInst: function ($input, opts) { + var tp_inst = new Timepicker(), + inlineSettings = {}, + fns = {}, + overrides, i; + + for (var attrName in this._defaults) { + if (this._defaults.hasOwnProperty(attrName)) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + } + + overrides = { + beforeShow: function (input, dp_inst) { + if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { + return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst); + } + }, + onChangeMonthYear: function (year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + // tp_inst._updateDateTime(dp_inst); + if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { + tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + } + }, + onClose: function (dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() !== '') { + tp_inst._updateDateTime(dp_inst); + } + if ($.isFunction(tp_inst._defaults.evnts.onClose)) { + tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); + } + } + }; + for (i in overrides) { + if (overrides.hasOwnProperty(i)) { + fns[i] = opts[i] || this._defaults[i] || null; + } + } + + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, { + evnts: fns, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { + return val.toUpperCase(); + }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { + return val.toUpperCase(); + }); + + // detect which units are supported + tp_inst.support = detectSupport( + tp_inst._defaults.timeFormat + + (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') + + (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); + + // controlType is string - key to our this._controls + if (typeof(tp_inst._defaults.controlType) === 'string') { + if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { + tp_inst._defaults.controlType = 'select'; + } + tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; + } + // controlType is an object and must implement create, options, value methods + else { + tp_inst.control = tp_inst._defaults.controlType; + } + + // prep the timezone options + var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60, + 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840]; + if (tp_inst._defaults.timezoneList !== null) { + timezoneList = tp_inst._defaults.timezoneList; + } + var tzl = timezoneList.length, tzi = 0, tzv = null; + if (tzl > 0 && typeof timezoneList[0] !== 'object') { + for (; tzi < tzl; tzi++) { + tzv = timezoneList[tzi]; + timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; + } + } + tp_inst._defaults.timezoneList = timezoneList; + + // set the default units + tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) : + ((new Date()).getTimezoneOffset() * -1); + tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin : + tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin : + tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin : + tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin : + tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec; + tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin : + tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (tp_inst._defaults.altField) { + tp_inst.$altInput = $(tp_inst._defaults.altField); + if (tp_inst._defaults.altRedirectFocus === true) { + tp_inst.$altInput.css({ + cursor: 'pointer' + }).focus(function () { + $input.trigger("focus"); + }); + } + } + + if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { + tp_inst._defaults.minDate = new Date(); + } + if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) { + tp_inst._defaults.maxDate = new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + } + if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + } + if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + } + if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + } + tp_inst.$input.bind('focus', function () { + tp_inst._onFocus(); + }); + + return tp_inst; + }, + + /* + * add our sliders to the calendar + */ + _addTimePicker: function (dp_inst) { + var currDT = $.trim((this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val()); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + this._afterInject(); + }, + + /* + * parse the time string from input value or _setTime + */ + _parseTime: function (timeString, withDate) { + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + if (withDate || !this._defaults.timeOnly) { + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + try { + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); + if (!parseRes.timeObj) { + return false; + } + $.extend(this, parseRes.timeObj); + } catch (err) { + $.timepicker.log("Error parsing the date/time string: " + err + + "\ndate/time string = " + timeString + + "\ntimeFormat = " + this._defaults.timeFormat + + "\ndateFormat = " + dp_dateFormat); + return false; + } + return true; + } else { + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); + if (!timeObj) { + return false; + } + $.extend(this, timeObj); + return true; + } + }, + + /* + * Handle callback option after injecting timepicker + */ + _afterInject: function() { + var o = this.inst.settings; + if ($.isFunction(o.afterInject)) { + o.afterInject.call(this); + } + }, + + /* + * generate and inject html for timepicker into ui datepicker + */ + _injectTimePicker: function () { + var $dp = this.inst.dpDiv, + o = this.inst.settings, + tp_inst = this, + litem = '', + uitem = '', + show = null, + max = {}, + gridSize = {}, + size = null, + i = 0, + l = 0; + + // Prevent displaying twice + if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { + var noDisplay = ' ui_tpicker_unit_hide', + html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + (o.oneLine && o.controlType === 'select' ? ' ui-timepicker-oneLine' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label' + ((o.showTime) ? '' : noDisplay) + '">' + o.timeText + '</dt>' + + '<dd class="ui_tpicker_time '+ ((o.showTime) ? '' : noDisplay) + '"><input class="ui_tpicker_time_input" ' + (o.timeInput ? '' : 'disabled') + '/></dd>'; + + // Create the markup + for (i = 0, l = this.units.length; i < l; i++) { + litem = this.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); + gridSize[litem] = 0; + + html += '<dt class="ui_tpicker_' + litem + '_label' + (show ? '' : noDisplay) + '">' + o[litem + 'Text'] + '</dt>' + + '<dd class="ui_tpicker_' + litem + (show ? '' : noDisplay) + '"><div class="ui_tpicker_' + litem + '_slider' + (show ? '' : noDisplay) + '"></div>'; + + if (show && o[litem + 'Grid'] > 0) { + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; + + if (litem === 'hour') { + for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); + html += '<td data-for="' + litem + '">' + tmph + '</td>'; + } + } + else { + for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>'; + } + } + + html += '</tr></table></div>'; + } + html += '</dd>'; + } + + // Timezone + var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; + html += '<dt class="ui_tpicker_timezone_label' + (showTz ? '' : noDisplay) + '">' + o.timezoneText + '</dt>'; + html += '<dd class="ui_tpicker_timezone' + (showTz ? '' : noDisplay) + '"></dd>'; + + // Create the elements from string + html += '</dl></div>'; + var $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + // add sliders, adjust grids, add events + for (i = 0, l = tp_inst.units.length; i < l; i++) { + litem = tp_inst.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // add the slider + tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); + + // adjust the grid and add click event + if (show && o[litem + 'Grid'] > 0) { + size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); + $tp.find('.ui_tpicker_' + litem + ' table').css({ + width: size + "%", + marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), + marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', + borderCollapse: 'collapse' + }).find("td").click(function (e) { + var $t = $(this), + h = $t.html(), + n = parseInt(h.replace(/[^0-9]/g), 10), + ap = h.replace(/[^apm]/ig), + f = $t.data('for'); // loses scope, so we use data-for + + if (f === 'hour') { + if (ap.indexOf('p') !== -1 && n < 12) { + n += 12; + } + else { + if (ap.indexOf('a') !== -1 && n === 12) { + n = 0; + } + } + } + + tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); + + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / gridSize[litem]) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + } // end if grid > 0 + } // end for loop + + // Add timezone options + this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val); + })); + if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") { + var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1; + if (local_timezone === this.timezone) { + selectLocalTimezone(tp_inst); + } else { + this.timezone_select.val(this.timezone); + } + } else { + if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") { + this.timezone_select.val(o.timezone); + } else { + selectLocalTimezone(tp_inst); + } + } + this.timezone_select.change(function () { + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + tp_inst._afterInject(); + }); + // End timezone options + + // inject timepicker into datepicker + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); + if ($buttonPanel.length) { + $buttonPanel.before($tp); + } else { + $dp.append($tp); + } + + this.$timeObj = $tp.find('.ui_tpicker_time_input'); + this.$timeObj.change(function () { + var timeFormat = tp_inst.inst.settings.timeFormat; + var parsedTime = $.datepicker.parseTime(timeFormat, this.value); + var update = new Date(); + if (parsedTime) { + update.setHours(parsedTime.hour); + update.setMinutes(parsedTime.minute); + update.setSeconds(parsedTime.second); + $.datepicker._setTime(tp_inst.inst, update); + } else { + this.value = tp_inst.formattedTime; + this.blur(); + } + }); + + if (this.inst !== null) { + var timeDefined = this.timeDefined; + this._onTimeChange(); + this.timeDefined = timeDefined; + } + + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ + if (this._defaults.addSliderAccess) { + var sliderAccessArgs = this._defaults.sliderAccessArgs, + rtl = this._defaults.isRTL; + sliderAccessArgs.isRTL = rtl; + + setTimeout(function () { // fix for inline mode + if ($tp.find('.ui-slider-access').length === 0) { + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); + + // fix any grids since sliders are shorter + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); + if (sliderAccessWidth) { + $tp.find('table:visible').each(function () { + var $g = $(this), + oldWidth = $g.outerWidth(), + oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''), + newWidth = oldWidth - sliderAccessWidth, + newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%', + css = { width: newWidth, marginRight: 0, marginLeft: 0 }; + css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft; + $g.css(css); + }); + } + } + }, 10); + } + // end slideAccess integration + + tp_inst._limitMinMaxDateTime(this.inst, true); + } + }, + + /* + * This function tries to limit the ability to go outside the + * min/max date range + */ + _limitMinMaxDateTime: function (dp_inst, adjustSliders) { + var o = this._defaults, + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); + + if (!this._defaults.showTimepicker) { + return; + } // No time so nothing to check here + + if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) { + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); + + if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) { + this.hourMinOriginal = o.hourMin; + this.minuteMinOriginal = o.minuteMin; + this.secondMinOriginal = o.secondMin; + this.millisecMinOriginal = o.millisecMin; + this.microsecMinOriginal = o.microsecMin; + } + + if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() === dp_date.getTime()) { + this._defaults.hourMin = minDateTime.getHours(); + if (this.hour <= this._defaults.hourMin) { + this.hour = this._defaults.hourMin; + this._defaults.minuteMin = minDateTime.getMinutes(); + if (this.minute <= this._defaults.minuteMin) { + this.minute = this._defaults.minuteMin; + this._defaults.secondMin = minDateTime.getSeconds(); + if (this.second <= this._defaults.secondMin) { + this.second = this._defaults.secondMin; + this._defaults.millisecMin = minDateTime.getMilliseconds(); + if (this.millisec <= this._defaults.millisecMin) { + this.millisec = this._defaults.millisecMin; + this._defaults.microsecMin = minDateTime.getMicroseconds(); + } else { + if (this.microsec < this._defaults.microsecMin) { + this.microsec = this._defaults.microsecMin; + } + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } else { + this._defaults.hourMin = this.hourMinOriginal; + this._defaults.minuteMin = this.minuteMinOriginal; + this._defaults.secondMin = this.secondMinOriginal; + this._defaults.millisecMin = this.millisecMinOriginal; + this._defaults.microsecMin = this.microsecMinOriginal; + } + } + + if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) { + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); + + if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) { + this.hourMaxOriginal = o.hourMax; + this.minuteMaxOriginal = o.minuteMax; + this.secondMaxOriginal = o.secondMax; + this.millisecMaxOriginal = o.millisecMax; + this.microsecMaxOriginal = o.microsecMax; + } + + if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() === dp_date.getTime()) { + this._defaults.hourMax = maxDateTime.getHours(); + if (this.hour >= this._defaults.hourMax) { + this.hour = this._defaults.hourMax; + this._defaults.minuteMax = maxDateTime.getMinutes(); + if (this.minute >= this._defaults.minuteMax) { + this.minute = this._defaults.minuteMax; + this._defaults.secondMax = maxDateTime.getSeconds(); + if (this.second >= this._defaults.secondMax) { + this.second = this._defaults.secondMax; + this._defaults.millisecMax = maxDateTime.getMilliseconds(); + if (this.millisec >= this._defaults.millisecMax) { + this.millisec = this._defaults.millisecMax; + this._defaults.microsecMax = maxDateTime.getMicroseconds(); + } else { + if (this.microsec > this._defaults.microsecMax) { + this.microsec = this._defaults.microsecMax; + } + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } else { + this._defaults.hourMax = this.hourMaxOriginal; + this._defaults.minuteMax = this.minuteMaxOriginal; + this._defaults.secondMax = this.secondMaxOriginal; + this._defaults.millisecMax = this.millisecMaxOriginal; + this._defaults.microsecMax = this.microsecMaxOriginal; + } + } + + if (dp_inst.settings.minTime!==null) { + var tempMinTime=new Date("01/01/1970 " + dp_inst.settings.minTime); + if (this.hour<tempMinTime.getHours()) { + this.hour=this._defaults.hourMin=tempMinTime.getHours(); + this.minute=this._defaults.minuteMin=tempMinTime.getMinutes(); + } else if (this.hour===tempMinTime.getHours() && this.minute<tempMinTime.getMinutes()) { + this.minute=this._defaults.minuteMin=tempMinTime.getMinutes(); + } else { + if (this._defaults.hourMin<tempMinTime.getHours()) { + this._defaults.hourMin=tempMinTime.getHours(); + this._defaults.minuteMin=tempMinTime.getMinutes(); + } else if (this._defaults.hourMin===tempMinTime.getHours()===this.hour && this._defaults.minuteMin<tempMinTime.getMinutes()) { + this._defaults.minuteMin=tempMinTime.getMinutes(); + } else { + this._defaults.minuteMin=0; + } + } + } + + if (dp_inst.settings.maxTime!==null) { + var tempMaxTime=new Date("01/01/1970 " + dp_inst.settings.maxTime); + if (this.hour>tempMaxTime.getHours()) { + this.hour=this._defaults.hourMax=tempMaxTime.getHours(); + this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else if (this.hour===tempMaxTime.getHours() && this.minute>tempMaxTime.getMinutes()) { + this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else { + if (this._defaults.hourMax>tempMaxTime.getHours()) { + this._defaults.hourMax=tempMaxTime.getHours(); + this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else if (this._defaults.hourMax===tempMaxTime.getHours()===this.hour && this._defaults.minuteMax>tempMaxTime.getMinutes()) { + this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else { + this._defaults.minuteMax=59; + } + } + } + + if (adjustSliders !== undefined && adjustSliders === true) { + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10), + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10), + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10), + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10), + microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10); + + if (this.hour_slider) { + this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax, step: this._defaults.stepHour }); + this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour)); + } + if (this.minute_slider) { + this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax, step: this._defaults.stepMinute }); + this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute)); + } + if (this.second_slider) { + this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax, step: this._defaults.stepSecond }); + this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond)); + } + if (this.millisec_slider) { + this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax, step: this._defaults.stepMillisec }); + this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec)); + } + if (this.microsec_slider) { + this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax, step: this._defaults.stepMicrosec }); + this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec)); + } + } + + }, + + /* + * when a slider moves, set the internal time... + * on time change is also called when the time is updated in the text field + */ + _onTimeChange: function () { + if (!this._defaults.showTimepicker) { + return; + } + var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false, + minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false, + second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false, + millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false, + microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false, + timezone = (this.timezone_select) ? this.timezone_select.val() : false, + o = this._defaults, + pickerTimeFormat = o.pickerTimeFormat || o.timeFormat, + pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix; + + if (typeof(hour) === 'object') { + hour = false; + } + if (typeof(minute) === 'object') { + minute = false; + } + if (typeof(second) === 'object') { + second = false; + } + if (typeof(millisec) === 'object') { + millisec = false; + } + if (typeof(microsec) === 'object') { + microsec = false; + } + if (typeof(timezone) === 'object') { + timezone = false; + } + + if (hour !== false) { + hour = parseInt(hour, 10); + } + if (minute !== false) { + minute = parseInt(minute, 10); + } + if (second !== false) { + second = parseInt(second, 10); + } + if (millisec !== false) { + millisec = parseInt(millisec, 10); + } + if (microsec !== false) { + microsec = parseInt(microsec, 10); + } + if (timezone !== false) { + timezone = timezone.toString(); + } + + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; + + // If the update was done in the input field, the input field should not be updated. + // If the update was done using the sliders, update the input field. + var hasChanged = ( + hour !== parseInt(this.hour,10) || // sliders should all be numeric + minute !== parseInt(this.minute,10) || + second !== parseInt(this.second,10) || + millisec !== parseInt(this.millisec,10) || + microsec !== parseInt(this.microsec,10) || + (this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || + (this.timezone !== null && timezone !== this.timezone.toString()) // could be numeric or "EST" format, so use toString() + ); + + if (hasChanged) { + + if (hour !== false) { + this.hour = hour; + } + if (minute !== false) { + this.minute = minute; + } + if (second !== false) { + this.second = second; + } + if (millisec !== false) { + this.millisec = millisec; + } + if (microsec !== false) { + this.microsec = microsec; + } + if (timezone !== false) { + this.timezone = timezone; + } + + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + this._limitMinMaxDateTime(this.inst, true); + } + if (this.support.ampm) { + this.ampm = ampm; + } + + // Updates the time within the timepicker + this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o); + if (this.$timeObj) { + if (pickerTimeFormat === o.timeFormat) { + this.$timeObj.val(this.formattedTime + pickerTimeSuffix); + } + else { + this.$timeObj.val($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix); + } + if (this.$timeObj[0].setSelectionRange) { + var sPos = this.$timeObj[0].selectionStart; + var ePos = this.$timeObj[0].selectionEnd; + this.$timeObj[0].setSelectionRange(sPos, ePos); + } + } + + this.timeDefined = true; + if (hasChanged) { + this._updateDateTime(); + //this.$input.focus(); // may automatically open the picker on setDate + } + }, + + /* + * call custom onSelect. + * bind to sliders slidestop, and grid click. + */ + _onSelectHandler: function () { + var onSelect = this._defaults.onSelect || this.inst.settings.onSelect; + var inputEl = this.$input ? this.$input[0] : null; + if (onSelect && inputEl) { + onSelect.apply(inputEl, [this.formattedDateTime, this]); + } + }, + + /* + * update our input with the new date time.. + */ + _updateDateTime: function (dp_inst) { + dp_inst = this.inst || dp_inst; + var dtTmp = (dp_inst.currentYear > 0? + new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) : + new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + dt = $.datepicker._daylightSavingAdjust(dtTmp), + //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), + //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)), + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), + formatCfg = $.datepicker._getFormatConfig(dp_inst), + timeAvailable = dt !== null && this.timeDefined; + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); + var formattedDateTime = this.formattedDate; + + // if a slider was changed but datepicker doesn't have a value yet, set it + if (dp_inst.lastVal === "") { dp_inst.currentYear = dp_inst.selectedYear; dp_inst.currentMonth = dp_inst.selectedMonth; dp_inst.currentDay = dp_inst.selectedDay; } - /* - * remove following lines to force every changes in date picker to change the input value - * Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. - * If the user manually empty the value in the input field, the date picker will never change selected value. - */ - //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) { - // return; - //} - - if (this._defaults.timeOnly === true) { - formattedDateTime = this.formattedTime; - } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { - formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; - } - - this.formattedDateTime = formattedDateTime; - - if (!this._defaults.showTimepicker) { - this.$input.val(this.formattedDate); - } else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) { - this.$altInput.val(this.formattedTime); - this.$input.val(this.formattedDate); - } else if (this.$altInput) { - this.$input.val(formattedDateTime); - var altFormattedDateTime = '', - altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator, - altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix; - - if (!this._defaults.timeOnly) { - if (this._defaults.altFormat) { - altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg); - } - else { - altFormattedDateTime = this.formattedDate; - } - - if (altFormattedDateTime) { - altFormattedDateTime += altSeparator; - } - } - - if (this._defaults.altTimeFormat) { - altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix; - } - else { - altFormattedDateTime += this.formattedTime + altTimeSuffix; - } - this.$altInput.val(altFormattedDateTime); - } else { - this.$input.val(formattedDateTime); - } - - this.$input.trigger("change"); - }, - - _onFocus: function () { - if (!this.$input.val() && this._defaults.defaultValue) { - this.$input.val(this._defaults.defaultValue); - var inst = $.datepicker._getInst(this.$input.get(0)), - tp_inst = $.datepicker._get(inst, 'timepicker'); - if (tp_inst) { - if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { - try { - $.datepicker._updateDatepicker(inst); - } catch (err) { - $.timepicker.log(err); - } - } - } - } - }, - - /* - * Small abstraction to control types - * We can add more, just be sure to follow the pattern: create, options, value - */ - _controls: { - // slider methods - slider: { - create: function (tp_inst, obj, unit, val, min, max, step) { - var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60 - return obj.prop('slide', null).slider({ - orientation: "horizontal", - value: rtl ? val * -1 : val, - min: rtl ? max * -1 : min, - max: rtl ? min * -1 : max, - step: step, - slide: function (event, ui) { - tp_inst.control.value(tp_inst, $(this), unit, rtl ? ui.value * -1 : ui.value); - tp_inst._onTimeChange(); - }, - stop: function (event, ui) { - tp_inst._onSelectHandler(); - } - }); - }, - options: function (tp_inst, obj, unit, opts, val) { - if (tp_inst._defaults.isRTL) { - if (typeof(opts) === 'string') { - if (opts === 'min' || opts === 'max') { - if (val !== undefined) { - return obj.slider(opts, val * -1); - } - return Math.abs(obj.slider(opts)); - } - return obj.slider(opts); - } - var min = opts.min, - max = opts.max; - opts.min = opts.max = null; - if (min !== undefined) { - opts.max = min * -1; - } - if (max !== undefined) { - opts.min = max * -1; - } - return obj.slider(opts); - } - if (typeof(opts) === 'string' && val !== undefined) { - return obj.slider(opts, val); - } - return obj.slider(opts); - }, - value: function (tp_inst, obj, unit, val) { - if (tp_inst._defaults.isRTL) { - if (val !== undefined) { - return obj.slider('value', val * -1); - } - return Math.abs(obj.slider('value')); - } - if (val !== undefined) { - return obj.slider('value', val); - } - return obj.slider('value'); - } - }, - // select methods - select: { - create: function (tp_inst, obj, unit, val, min, max, step) { - var sel = '<select class="ui-timepicker-select" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">', - format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat; - - for (var i = min; i <= max; i += step) { - sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>'; - if (unit === 'hour') { - sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults); - } - else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; } - else {sel += '0' + i.toString(); } - sel += '</option>'; - } - sel += '</select>'; - - obj.children('select').remove(); - - $(sel).appendTo(obj).change(function (e) { - tp_inst._onTimeChange(); - tp_inst._onSelectHandler(); - }); - - return obj; - }, - options: function (tp_inst, obj, unit, opts, val) { - var o = {}, - $t = obj.children('select'); - if (typeof(opts) === 'string') { - if (val === undefined) { - return $t.data(opts); - } - o[opts] = val; - } - else { o = opts; } - return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step')); - }, - value: function (tp_inst, obj, unit, val) { - var $t = obj.children('select'); - if (val !== undefined) { - return $t.val(val); - } - return $t.val(); - } - } - } // end _controls - - }); - - $.fn.extend({ - /* - * shorthand just to use timepicker. - */ - timepicker: function (o) { - o = o || {}; - var tmp_args = Array.prototype.slice.call(arguments); - - if (typeof o === 'object') { - tmp_args[0] = $.extend(o, { - timeOnly: true - }); - } - - return $(this).each(function () { - $.fn.datetimepicker.apply($(this), tmp_args); - }); - }, - - /* - * extend timepicker to datepicker - */ - datetimepicker: function (o) { - o = o || {}; - var tmp_args = arguments; - - if (typeof(o) === 'string') { - if (o === 'getDate') { - return $.fn.datepicker.apply($(this[0]), tmp_args); - } else { - return this.each(function () { - var $t = $(this); - $t.datepicker.apply($t, tmp_args); - }); - } - } else { - return this.each(function () { - var $t = $(this); - $t.datepicker($.timepicker._newInst($t, o)._defaults); - }); - } - } - }); - - /* - * Public Utility to parse date and time - */ - $.datepicker.parseDateTime = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { - var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings); - if (parseRes.timeObj) { - var t = parseRes.timeObj; - parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec); - parseRes.date.setMicroseconds(t.microsec); - } - - return parseRes.date; - }; - - /* - * Public utility to parse time - */ - $.datepicker.parseTime = function (timeFormat, timeString, options) { - var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}), - iso8601 = (timeFormat.replace(/\'.*?\'/g, '').indexOf('Z') !== -1); - - // Strict parse requires the timeString to match the timeFormat exactly - var strictParse = function (f, s, o) { - - // pattern for standard and localized AM/PM markers - var getPatternAmpm = function (amNames, pmNames) { - var markers = []; - if (amNames) { - $.merge(markers, amNames); - } - if (pmNames) { - $.merge(markers, pmNames); - } - markers = $.map(markers, function (val) { - return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); - }); - return '(' + markers.join('|') + ')?'; - }; - - // figure out position of time elements.. cause js cant do named captures - var getFormatPositions = function (timeFormat) { - var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g), - orders = { - h: -1, - m: -1, - s: -1, - l: -1, - c: -1, - t: -1, - z: -1 - }; - - if (finds) { - for (var i = 0; i < finds.length; i++) { - if (orders[finds[i].toString().charAt(0)] === -1) { - orders[finds[i].toString().charAt(0)] = i + 1; - } - } - } - return orders; - }; - - var regstr = '^' + f.toString() - .replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { - var ml = match.length; - switch (match.charAt(0).toLowerCase()) { - case 'h': - return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; - case 'm': - return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; - case 's': - return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; - case 'l': - return '(\\d?\\d?\\d)'; - case 'c': - return '(\\d?\\d?\\d)'; - case 'z': - return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?'; - case 't': - return getPatternAmpm(o.amNames, o.pmNames); - default: // literal escaped in quotes - return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?'; - } - }) - .replace(/\s/g, '\\s?') + - o.timeSuffix + '$', - order = getFormatPositions(f), - ampm = '', - treg; - - treg = s.match(new RegExp(regstr, 'i')); - - var resTime = { - hour: 0, - minute: 0, - second: 0, - millisec: 0, - microsec: 0 - }; - - if (treg) { - if (order.t !== -1) { - if (treg[order.t] === undefined || treg[order.t].length === 0) { - ampm = ''; - resTime.ampm = ''; - } else { - ampm = $.inArray(treg[order.t].toUpperCase(), o.amNames) !== -1 ? 'AM' : 'PM'; - resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0]; - } - } - - if (order.h !== -1) { - if (ampm === 'AM' && treg[order.h] === '12') { - resTime.hour = 0; // 12am = 0 hour - } else { - if (ampm === 'PM' && treg[order.h] !== '12') { - resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12 - } else { - resTime.hour = Number(treg[order.h]); - } - } - } - - if (order.m !== -1) { - resTime.minute = Number(treg[order.m]); - } - if (order.s !== -1) { - resTime.second = Number(treg[order.s]); - } - if (order.l !== -1) { - resTime.millisec = Number(treg[order.l]); - } - if (order.c !== -1) { - resTime.microsec = Number(treg[order.c]); - } - if (order.z !== -1 && treg[order.z] !== undefined) { - resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]); - } - - - return resTime; - } - return false; - };// end strictParse - - // First try JS Date, if that fails, use strictParse - var looseParse = function (f, s, o) { - try { - var d = new Date('2012-01-01 ' + s); - if (isNaN(d.getTime())) { - d = new Date('2012-01-01T' + s); - if (isNaN(d.getTime())) { - d = new Date('01/01/2012 ' + s); - if (isNaN(d.getTime())) { - throw "Unable to parse time with native Date: " + s; - } - } - } - - return { - hour: d.getHours(), - minute: d.getMinutes(), - second: d.getSeconds(), - millisec: d.getMilliseconds(), - microsec: d.getMicroseconds(), - timezone: d.getTimezoneOffset() * -1 - }; - } - catch (err) { - try { - return strictParse(f, s, o); - } - catch (err2) { - $.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f); - } - } - return false; - }; // end looseParse - - if (typeof o.parse === "function") { - return o.parse(timeFormat, timeString, o); - } - if (o.parse === 'loose') { - return looseParse(timeFormat, timeString, o); - } - return strictParse(timeFormat, timeString, o); - }; - - /** - * Public utility to format the time - * @param {string} format format of the time - * @param {Object} time Object not a Date for timezones - * @param {Object} [options] essentially the regional[].. amNames, pmNames, ampm - * @returns {string} the formatted time - */ - $.datepicker.formatTime = function (format, time, options) { - options = options || {}; - options = $.extend({}, $.timepicker._defaults, options); - time = $.extend({ - hour: 0, - minute: 0, - second: 0, - millisec: 0, - microsec: 0, - timezone: null - }, time); - - var tmptime = format, - ampmName = options.amNames[0], - hour = parseInt(time.hour, 10); - - if (hour > 11) { - ampmName = options.pmNames[0]; - } - - tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { - switch (match) { - case 'HH': - return ('0' + hour).slice(-2); - case 'H': - return hour; - case 'hh': - return ('0' + convert24to12(hour)).slice(-2); - case 'h': - return convert24to12(hour); - case 'mm': - return ('0' + time.minute).slice(-2); - case 'm': - return time.minute; - case 'ss': - return ('0' + time.second).slice(-2); - case 's': - return time.second; - case 'l': - return ('00' + time.millisec).slice(-3); - case 'c': - return ('00' + time.microsec).slice(-3); - case 'z': - return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, false); - case 'Z': - return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, true); - case 'T': - return ampmName.charAt(0).toUpperCase(); - case 'TT': - return ampmName.toUpperCase(); - case 't': - return ampmName.charAt(0).toLowerCase(); - case 'tt': - return ampmName.toLowerCase(); - default: - return match.replace(/'/g, ""); - } - }); - - return tmptime; - }; - - /* - * the bad hack :/ override datepicker so it doesn't close on select - // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 - */ - $.datepicker._base_selectDate = $.datepicker._selectDate; - $.datepicker._selectDate = function (id, dateStr) { - var inst = this._getInst($(id)[0]), - tp_inst = this._get(inst, 'timepicker'); - - if (tp_inst) { - tp_inst._limitMinMaxDateTime(inst, true); - inst.inline = inst.stay_open = true; - //This way the onSelect handler called from calendarpicker get the full dateTime - this._base_selectDate(id, dateStr); - inst.inline = inst.stay_open = false; - this._notifyChange(inst); - this._updateDatepicker(inst); - } else { - this._base_selectDate(id, dateStr); - } - }; - - /* - * second bad hack :/ override datepicker so it triggers an event when changing the input field - * and does not redraw the datepicker on every selectDate event - */ - $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; - $.datepicker._updateDatepicker = function (inst) { - - // don't popup the datepicker if there is another instance already opened - var input = inst.input[0]; - if ($.datepicker._curInst && $.datepicker._curInst !== inst && $.datepicker._datepickerShowing && $.datepicker._lastInput !== input) { - return; - } - - if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { - - this._base_updateDatepicker(inst); - - // Reload the time control when changing something in the input text field. - var tp_inst = this._get(inst, 'timepicker'); - if (tp_inst) { - tp_inst._addTimePicker(inst); - } - } - }; - - /* - * third bad hack :/ override datepicker so it allows spaces and colon in the input field - */ - $.datepicker._base_doKeyPress = $.datepicker._doKeyPress; - $.datepicker._doKeyPress = function (event) { - var inst = $.datepicker._getInst(event.target), - tp_inst = $.datepicker._get(inst, 'timepicker'); - - if (tp_inst) { - if ($.datepicker._get(inst, 'constrainInput')) { - var ampm = tp_inst.support.ampm, - tz = tp_inst._defaults.showTimezone !== null ? tp_inst._defaults.showTimezone : tp_inst.support.timezone, - dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), - datetimeChars = tp_inst._defaults.timeFormat.toString() - .replace(/[hms]/g, '') - .replace(/TT/g, ampm ? 'APM' : '') - .replace(/Tt/g, ampm ? 'AaPpMm' : '') - .replace(/tT/g, ampm ? 'AaPpMm' : '') - .replace(/T/g, ampm ? 'AP' : '') - .replace(/tt/g, ampm ? 'apm' : '') - .replace(/t/g, ampm ? 'ap' : '') + - " " + tp_inst._defaults.separator + - tp_inst._defaults.timeSuffix + - (tz ? tp_inst._defaults.timezoneList.join('') : '') + - (tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) + - dateChars, - chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); - return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); - } - } - - return $.datepicker._base_doKeyPress(event); - }; - - /* - * Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField - * Update any alternate field to synchronise with the main field. - */ - $.datepicker._base_updateAlternate = $.datepicker._updateAlternate; - $.datepicker._updateAlternate = function (inst) { - var tp_inst = this._get(inst, 'timepicker'); - if (tp_inst) { - var altField = tp_inst._defaults.altField; - if (altField) { // update alternate field too - var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat, - date = this._getDate(inst), - formatCfg = $.datepicker._getFormatConfig(inst), - altFormattedDateTime = '', - altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, - altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix, - altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat; - - altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix; - if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) { - if (tp_inst._defaults.altFormat) { - altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime; - } - else { - altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime; - } - } - $(altField).val(altFormattedDateTime); - } - } - else { - $.datepicker._base_updateAlternate(inst); - } - }; - - /* - * Override key up event to sync manual input changes. - */ - $.datepicker._base_doKeyUp = $.datepicker._doKeyUp; - $.datepicker._doKeyUp = function (event) { - var inst = $.datepicker._getInst(event.target), - tp_inst = $.datepicker._get(inst, 'timepicker'); - - if (tp_inst) { - if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { - try { - $.datepicker._updateDatepicker(inst); - } catch (err) { - $.timepicker.log(err); - } - } - } - - return $.datepicker._base_doKeyUp(event); - }; - - /* - * override "Today" button to also grab the time. - */ - $.datepicker._base_gotoToday = $.datepicker._gotoToday; - $.datepicker._gotoToday = function (id) { - var inst = this._getInst($(id)[0]), - $dp = inst.dpDiv; - this._base_gotoToday(id); - var tp_inst = this._get(inst, 'timepicker'); - selectLocalTimezone(tp_inst); - var now = new Date(); - this._setTime(inst, now); - $('.ui-datepicker-today', $dp).click(); - }; - - /* - * Disable & enable the Time in the datetimepicker - */ - $.datepicker._disableTimepickerDatepicker = function (target) { - var inst = this._getInst(target); - if (!inst) { - return; - } - - var tp_inst = this._get(inst, 'timepicker'); - $(target).datepicker('getDate'); // Init selected[Year|Month|Day] - if (tp_inst) { - inst.settings.showTimepicker = false; - tp_inst._defaults.showTimepicker = false; - tp_inst._updateDateTime(inst); - } - }; - - $.datepicker._enableTimepickerDatepicker = function (target) { - var inst = this._getInst(target); - if (!inst) { - return; - } - - var tp_inst = this._get(inst, 'timepicker'); - $(target).datepicker('getDate'); // Init selected[Year|Month|Day] - if (tp_inst) { - inst.settings.showTimepicker = true; - tp_inst._defaults.showTimepicker = true; - tp_inst._addTimePicker(inst); // Could be disabled on page load - tp_inst._updateDateTime(inst); - } - }; - - /* - * Create our own set time function - */ - $.datepicker._setTime = function (inst, date) { - var tp_inst = this._get(inst, 'timepicker'); - if (tp_inst) { - var defaults = tp_inst._defaults; - - // calling _setTime with no date sets time to defaults - tp_inst.hour = date ? date.getHours() : defaults.hour; - tp_inst.minute = date ? date.getMinutes() : defaults.minute; - tp_inst.second = date ? date.getSeconds() : defaults.second; - tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec; - tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec; - - //check if within min/max times.. - tp_inst._limitMinMaxDateTime(inst, true); - - tp_inst._onTimeChange(); - tp_inst._updateDateTime(inst); - } - }; - - /* - * Create new public method to set only time, callable as $().datepicker('setTime', date) - */ - $.datepicker._setTimeDatepicker = function (target, date, withDate) { - var inst = this._getInst(target); - if (!inst) { - return; - } - - var tp_inst = this._get(inst, 'timepicker'); - - if (tp_inst) { - this._setDateFromField(inst); - var tp_date; - if (date) { - if (typeof date === "string") { - tp_inst._parseTime(date, withDate); - tp_date = new Date(); - tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); - tp_date.setMicroseconds(tp_inst.microsec); - } else { - tp_date = new Date(date.getTime()); - tp_date.setMicroseconds(date.getMicroseconds()); - } - if (tp_date.toString() === 'Invalid Date') { - tp_date = undefined; - } - this._setTime(inst, tp_date); - } - } - - }; - - /* - * override setDate() to allow setting time too within Date object - */ - $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; - $.datepicker._setDateDatepicker = function (target, date) { - var inst = this._getInst(target); - if (!inst) { - return; - } - - if (typeof(date) === 'string') { - date = new Date(date); - if (!date.getTime()) { - $.timepicker.log("Error creating Date object from string."); - } - } - - var tp_inst = this._get(inst, 'timepicker'); - var tp_date; - if (date instanceof Date) { - tp_date = new Date(date.getTime()); - tp_date.setMicroseconds(date.getMicroseconds()); - } else { - tp_date = date; - } - - // This is important if you are using the timezone option, javascript's Date - // object will only return the timezone offset for the current locale, so we - // adjust it accordingly. If not using timezone option this won't matter.. - // If a timezone is different in tp, keep the timezone as is - if (tp_inst && tp_date) { - // look out for DST if tz wasn't specified - if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { - tp_inst.timezone = tp_date.getTimezoneOffset() * -1; - } - date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); - tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone); - } - - this._updateDatepicker(inst); - this._base_setDateDatepicker.apply(this, arguments); - this._setTimeDatepicker(target, tp_date, true); - }; - - /* - * override getDate() to allow getting time too within Date object - */ - $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; - $.datepicker._getDateDatepicker = function (target, noDefault) { - var inst = this._getInst(target); - if (!inst) { - return; - } - - var tp_inst = this._get(inst, 'timepicker'); - - if (tp_inst) { - // if it hasn't yet been defined, grab from field - if (inst.lastVal === undefined) { - this._setDateFromField(inst, noDefault); - } - - var date = this._getDate(inst); - if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { - date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); - date.setMicroseconds(tp_inst.microsec); - - // This is important if you are using the timezone option, javascript's Date - // object will only return the timezone offset for the current locale, so we - // adjust it accordingly. If not using timezone option this won't matter.. - if (tp_inst.timezone != null) { - // look out for DST if tz wasn't specified - if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { - tp_inst.timezone = date.getTimezoneOffset() * -1; - } - date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); - } - } - return date; - } - return this._base_getDateDatepicker(target, noDefault); - }; - - /* - * override parseDate() because UI 1.8.14 throws an error about "Extra characters" - * An option in datapicker to ignore extra format characters would be nicer. - */ - $.datepicker._base_parseDate = $.datepicker.parseDate; - $.datepicker.parseDate = function (format, value, settings) { - var date; - try { - date = this._base_parseDate(format, value, settings); - } catch (err) { - // Hack! The error message ends with a colon, a space, and - // the "extra" characters. We rely on that instead of - // attempting to perfectly reproduce the parsing algorithm. - if (err.indexOf(":") >= 0) { - date = this._base_parseDate(format, value.substring(0, value.length - (err.length - err.indexOf(':') - 2)), settings); - $.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format); - } else { - throw err; - } - } - return date; - }; - - /* - * override formatDate to set date with time to the input - */ - $.datepicker._base_formatDate = $.datepicker._formatDate; - $.datepicker._formatDate = function (inst, day, month, year) { - var tp_inst = this._get(inst, 'timepicker'); - if (tp_inst) { - tp_inst._updateDateTime(inst); - return tp_inst.$input.val(); - } - return this._base_formatDate(inst); - }; - - /* - * override options setter to add time to maxDate(Time) and minDate(Time). MaxDate - */ - $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; - $.datepicker._optionDatepicker = function (target, name, value) { - var inst = this._getInst(target), - name_clone; - if (!inst) { - return null; - } - - var tp_inst = this._get(inst, 'timepicker'); - if (tp_inst) { - var min = null, - max = null, - onselect = null, - overrides = tp_inst._defaults.evnts, - fns = {}, - prop; - if (typeof name === 'string') { // if min/max was set with the string - if (name === 'minDate' || name === 'minDateTime') { - min = value; - } else if (name === 'maxDate' || name === 'maxDateTime') { - max = value; - } else if (name === 'onSelect') { - onselect = value; - } else if (overrides.hasOwnProperty(name)) { - if (typeof (value) === 'undefined') { - return overrides[name]; - } - fns[name] = value; - name_clone = {}; //empty results in exiting function after overrides updated - } - } else if (typeof name === 'object') { //if min/max was set with the JSON - if (name.minDate) { - min = name.minDate; - } else if (name.minDateTime) { - min = name.minDateTime; - } else if (name.maxDate) { - max = name.maxDate; - } else if (name.maxDateTime) { - max = name.maxDateTime; - } - for (prop in overrides) { - if (overrides.hasOwnProperty(prop) && name[prop]) { - fns[prop] = name[prop]; - } - } - } - for (prop in fns) { - if (fns.hasOwnProperty(prop)) { - overrides[prop] = fns[prop]; - if (!name_clone) { name_clone = $.extend({}, name); } - delete name_clone[prop]; - } - } - if (name_clone && isEmptyObject(name_clone)) { return; } - if (min) { //if min was set - if (min === 0) { - min = new Date(); - } else { - min = new Date(min); - } - tp_inst._defaults.minDate = min; - tp_inst._defaults.minDateTime = min; - } else if (max) { //if max was set - if (max === 0) { - max = new Date(); - } else { - max = new Date(max); - } - tp_inst._defaults.maxDate = max; - tp_inst._defaults.maxDateTime = max; - } else if (onselect) { - tp_inst._defaults.onSelect = onselect; - } - } - if (value === undefined) { - return this._base_optionDatepicker.call($.datepicker, target, name); - } - return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value); - }; - - /* - * jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype, - * it will return false for all objects - */ - var isEmptyObject = function (obj) { - var prop; - for (prop in obj) { - if (obj.hasOwnProperty(prop)) { - return false; - } - } - return true; - }; - - /* - * jQuery extend now ignores nulls! - */ - var extendRemove = function (target, props) { - $.extend(target, props); - for (var name in props) { - if (props[name] === null || props[name] === undefined) { - target[name] = props[name]; - } - } - return target; - }; - - /* - * Determine by the time format which units are supported - * Returns an object of booleans for each unit - */ - var detectSupport = function (timeFormat) { - var tf = timeFormat.replace(/'.*?'/g, '').toLowerCase(), // removes literals - isIn = function (f, t) { // does the format contain the token? - return f.indexOf(t) !== -1 ? true : false; - }; - return { - hour: isIn(tf, 'h'), - minute: isIn(tf, 'm'), - second: isIn(tf, 's'), - millisec: isIn(tf, 'l'), - microsec: isIn(tf, 'c'), - timezone: isIn(tf, 'z'), - ampm: isIn(tf, 't') && isIn(timeFormat, 'h'), - iso8601: isIn(timeFormat, 'Z') - }; - }; - - /* - * Converts 24 hour format into 12 hour - * Returns 12 hour without leading 0 - */ - var convert24to12 = function (hour) { - hour %= 12; - - if (hour === 0) { - hour = 12; - } - - return String(hour); - }; - - var computeEffectiveSetting = function (settings, property) { - return settings && settings[property] ? settings[property] : $.timepicker._defaults[property]; - }; - - /* - * Splits datetime string into date and time substrings. - * Throws exception when date can't be parsed - * Returns {dateString: dateString, timeString: timeString} - */ - var splitDateTime = function (dateTimeString, timeSettings) { - // The idea is to get the number separator occurrences in datetime and the time format requested (since time has - // fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split. - var separator = computeEffectiveSetting(timeSettings, 'separator'), - format = computeEffectiveSetting(timeSettings, 'timeFormat'), - timeParts = format.split(separator), // how many occurrences of separator may be in our format? - timePartsLen = timeParts.length, - allParts = dateTimeString.split(separator), - allPartsLen = allParts.length; - - if (allPartsLen > 1) { - return { - dateString: allParts.splice(0, allPartsLen - timePartsLen).join(separator), - timeString: allParts.splice(0, timePartsLen).join(separator) - }; - } - - return { - dateString: dateTimeString, - timeString: '' - }; - }; - - /* - * Internal function to parse datetime interval - * Returns: {date: Date, timeObj: Object}, where - * date - parsed date without time (type Date) - * timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional - */ - var parseDateTimeInternal = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { - var date, - parts, - parsedTime; - - parts = splitDateTime(dateTimeString, timeSettings); - date = $.datepicker._base_parseDate(dateFormat, parts.dateString, dateSettings); - - if (parts.timeString === '') { - return { - date: date - }; - } - - parsedTime = $.datepicker.parseTime(timeFormat, parts.timeString, timeSettings); - - if (!parsedTime) { - throw 'Wrong time format'; - } - - return { - date: date, - timeObj: parsedTime - }; - }; - - /* - * Internal function to set timezone_select to the local timezone - */ - var selectLocalTimezone = function (tp_inst, date) { - if (tp_inst && tp_inst.timezone_select) { - var now = date || new Date(); - tp_inst.timezone_select.val(-now.getTimezoneOffset()); - } - }; - - /* - * Create a Singleton Instance - */ - $.timepicker = new Timepicker(); - - /** - * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5) - * @param {number} tzMinutes if not a number, less than -720 (-1200), or greater than 840 (+1400) this value is returned - * @param {boolean} iso8601 if true formats in accordance to iso8601 "+12:45" - * @return {string} - */ - $.timepicker.timezoneOffsetString = function (tzMinutes, iso8601) { - if (isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720) { - return tzMinutes; - } - - var off = tzMinutes, - minutes = off % 60, - hours = (off - minutes) / 60, - iso = iso8601 ? ':' : '', - tz = (off >= 0 ? '+' : '-') + ('0' + Math.abs(hours)).slice(-2) + iso + ('0' + Math.abs(minutes)).slice(-2); - - if (tz === '+00:00') { - return 'Z'; - } - return tz; - }; - - /** - * Get the number in minutes that represents a timezone string - * @param {string} tzString formatted like "+0500", "-1245", "Z" - * @return {number} the offset minutes or the original string if it doesn't match expectations - */ - $.timepicker.timezoneOffsetNumber = function (tzString) { - var normalized = tzString.toString().replace(':', ''); // excuse any iso8601, end up with "+1245" - - if (normalized.toUpperCase() === 'Z') { // if iso8601 with Z, its 0 minute offset - return 0; - } - - if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back - return tzString; - } - - return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus - ((parseInt(normalized.substr(1, 2), 10) * 60) + // hours (converted to minutes) - parseInt(normalized.substr(3, 2), 10))); // minutes - }; - - /** - * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate) - * @param {Date} date - * @param {string} toTimezone formatted like "+0500", "-1245" - * @return {Date} - */ - $.timepicker.timezoneAdjust = function (date, toTimezone) { - var toTz = $.timepicker.timezoneOffsetNumber(toTimezone); - if (!isNaN(toTz)) { - date.setMinutes(date.getMinutes() + -date.getTimezoneOffset() - toTz); - } - return date; - }; - - /** - * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to - * enforce date range limits. - * n.b. The input value must be correctly formatted (reformatting is not supported) - * @param {Element} startTime - * @param {Element} endTime - * @param {Object} options Options for the timepicker() call - * @return {jQuery} - */ - $.timepicker.timeRange = function (startTime, endTime, options) { - return $.timepicker.handleRange('timepicker', startTime, endTime, options); - }; - - /** - * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to - * enforce date range limits. - * @param {Element} startTime - * @param {Element} endTime - * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, - * a boolean value that can be used to reformat the input values to the `dateFormat`. - * @param {string} method Can be used to specify the type of picker to be added - * @return {jQuery} - */ - $.timepicker.datetimeRange = function (startTime, endTime, options) { - $.timepicker.handleRange('datetimepicker', startTime, endTime, options); - }; - - /** - * Calls `datepicker` on the `startTime` and `endTime` elements, and configures them to - * enforce date range limits. - * @param {Element} startTime - * @param {Element} endTime - * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, - * a boolean value that can be used to reformat the input values to the `dateFormat`. - * @return {jQuery} - */ - $.timepicker.dateRange = function (startTime, endTime, options) { - $.timepicker.handleRange('datepicker', startTime, endTime, options); - }; - - /** - * Calls `method` on the `startTime` and `endTime` elements, and configures them to - * enforce date range limits. - * @param {string} method Can be used to specify the type of picker to be added - * @param {Element} startTime - * @param {Element} endTime - * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, - * a boolean value that can be used to reformat the input values to the `dateFormat`. - * @return {jQuery} - */ - $.timepicker.handleRange = function (method, startTime, endTime, options) { - options = $.extend({}, { - minInterval: 0, // min allowed interval in milliseconds - maxInterval: 0, // max allowed interval in milliseconds - start: {}, // options for start picker - end: {} // options for end picker - }, options); - - function checkDates(changed, other) { - var startdt = startTime[method]('getDate'), - enddt = endTime[method]('getDate'), - changeddt = changed[method]('getDate'); - - if (startdt !== null) { - var minDate = new Date(startdt.getTime()), - maxDate = new Date(startdt.getTime()); - - minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval); - maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval); - - if (options.minInterval > 0 && minDate > enddt) { // minInterval check - endTime[method]('setDate', minDate); - } - else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check - endTime[method]('setDate', maxDate); - } - else if (startdt > enddt) { - other[method]('setDate', changeddt); - } - } - } - - function selected(changed, other, option) { - if (!changed.val()) { - return; - } - var date = changed[method].call(changed, 'getDate'); - if (date !== null && options.minInterval > 0) { - if (option === 'minDate') { - date.setMilliseconds(date.getMilliseconds() + options.minInterval); - } - if (option === 'maxDate') { - date.setMilliseconds(date.getMilliseconds() - options.minInterval); - } - } - if (date.getTime) { - other[method].call(other, 'option', option, date); - } - } - - $.fn[method].call(startTime, $.extend({ - onClose: function (dateText, inst) { - checkDates($(this), endTime); - }, - onSelect: function (selectedDateTime) { - selected($(this), endTime, 'minDate'); - } - }, options, options.start)); - $.fn[method].call(endTime, $.extend({ - onClose: function (dateText, inst) { - checkDates($(this), startTime); - }, - onSelect: function (selectedDateTime) { - selected($(this), startTime, 'maxDate'); - } - }, options, options.end)); - - checkDates(startTime, endTime); - selected(startTime, endTime, 'minDate'); - selected(endTime, startTime, 'maxDate'); - return $([startTime.get(0), endTime.get(0)]); - }; - - /** - * Log error or data to the console during error or debugging - * @param {Object} err pass any type object to log to the console during error or debugging - * @return {void} - */ - $.timepicker.log = function (err) { - if (window.console) { - window.console.log(err); - } - }; - - /* - * Add util object to allow access to private methods for testability. - */ - $.timepicker._util = { - _extendRemove: extendRemove, - _isEmptyObject: isEmptyObject, - _convert24to12: convert24to12, - _detectSupport: detectSupport, - _selectLocalTimezone: selectLocalTimezone, - _computeEffectiveSetting: computeEffectiveSetting, - _splitDateTime: splitDateTime, - _parseDateTimeInternal: parseDateTimeInternal - }; - - /* - * Microsecond support - */ - if (!Date.prototype.getMicroseconds) { - Date.prototype.microseconds = 0; - Date.prototype.getMicroseconds = function () { return this.microseconds; }; - Date.prototype.setMicroseconds = function (m) { - this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000)); - this.microseconds = m % 1000; - return this; - }; - } - - /* - * Keep up with the version - */ - $.timepicker.version = "1.4.3"; - -})); \ No newline at end of file + /* + * remove following lines to force every changes in date picker to change the input value + * Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. + * If the user manually empty the value in the input field, the date picker will never change selected value. + */ + //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) { + // return; + //} + + if (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === false) { + formattedDateTime = this.formattedTime; + } else if ((this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) || (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === true)) { + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; + } + + this.formattedDateTime = formattedDateTime; + + if (!this._defaults.showTimepicker) { + this.$input.val(this.formattedDate); + } else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) { + this.$altInput.val(this.formattedTime); + this.$input.val(this.formattedDate); + } else if (this.$altInput) { + this.$input.val(formattedDateTime); + var altFormattedDateTime = '', + altSeparator = this._defaults.altSeparator !== null ? this._defaults.altSeparator : this._defaults.separator, + altTimeSuffix = this._defaults.altTimeSuffix !== null ? this._defaults.altTimeSuffix : this._defaults.timeSuffix; + + if (!this._defaults.timeOnly) { + if (this._defaults.altFormat) { + altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg); + } + else { + altFormattedDateTime = this.formattedDate; + } + + if (altFormattedDateTime) { + altFormattedDateTime += altSeparator; + } + } + + if (this._defaults.altTimeFormat !== null) { + altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix; + } + else { + altFormattedDateTime += this.formattedTime + altTimeSuffix; + } + this.$altInput.val(altFormattedDateTime); + } else { + this.$input.val(formattedDateTime); + } + + this.$input.trigger("change"); + }, + + _onFocus: function () { + if (!this.$input.val() && this._defaults.defaultValue) { + this.$input.val(this._defaults.defaultValue); + var inst = $.datepicker._getInst(this.$input.get(0)), + tp_inst = $.datepicker._get(inst, 'timepicker'); + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } catch (err) { + $.timepicker.log(err); + } + } + } + } + }, + + /* + * Small abstraction to control types + * We can add more, just be sure to follow the pattern: create, options, value + */ + _controls: { + // slider methods + slider: { + create: function (tp_inst, obj, unit, val, min, max, step) { + var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60 + return obj.prop('slide', null).slider({ + orientation: "horizontal", + value: rtl ? val * -1 : val, + min: rtl ? max * -1 : min, + max: rtl ? min * -1 : max, + step: step, + slide: function (event, ui) { + tp_inst.control.value(tp_inst, $(this), unit, rtl ? ui.value * -1 : ui.value); + tp_inst._onTimeChange(); + }, + stop: function (event, ui) { + tp_inst._onSelectHandler(); + } + }); + }, + options: function (tp_inst, obj, unit, opts, val) { + if (tp_inst._defaults.isRTL) { + if (typeof(opts) === 'string') { + if (opts === 'min' || opts === 'max') { + if (val !== undefined) { + return obj.slider(opts, val * -1); + } + return Math.abs(obj.slider(opts)); + } + return obj.slider(opts); + } + var min = opts.min, + max = opts.max; + opts.min = opts.max = null; + if (min !== undefined) { + opts.max = min * -1; + } + if (max !== undefined) { + opts.min = max * -1; + } + return obj.slider(opts); + } + if (typeof(opts) === 'string' && val !== undefined) { + return obj.slider(opts, val); + } + return obj.slider(opts); + }, + value: function (tp_inst, obj, unit, val) { + if (tp_inst._defaults.isRTL) { + if (val !== undefined) { + return obj.slider('value', val * -1); + } + return Math.abs(obj.slider('value')); + } + if (val !== undefined) { + return obj.slider('value', val); + } + return obj.slider('value'); + } + }, + // select methods + select: { + create: function (tp_inst, obj, unit, val, min, max, step) { + var sel = '<select class="ui-timepicker-select ui-state-default ui-corner-all" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">', + format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat; + + for (var i = min; i <= max; i += step) { + sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>'; + if (unit === 'hour') { + sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults); + } + else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; } + else {sel += '0' + i.toString(); } + sel += '</option>'; + } + sel += '</select>'; + + obj.children('select').remove(); + + $(sel).appendTo(obj).change(function (e) { + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + tp_inst._afterInject(); + }); + + return obj; + }, + options: function (tp_inst, obj, unit, opts, val) { + var o = {}, + $t = obj.children('select'); + if (typeof(opts) === 'string') { + if (val === undefined) { + return $t.data(opts); + } + o[opts] = val; + } + else { o = opts; } + return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min>=0 ? o.min : $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step')); + }, + value: function (tp_inst, obj, unit, val) { + var $t = obj.children('select'); + if (val !== undefined) { + return $t.val(val); + } + return $t.val(); + } + } + } // end _controls + + }); + + $.fn.extend({ + /* + * shorthand just to use timepicker. + */ + timepicker: function (o) { + o = o || {}; + var tmp_args = Array.prototype.slice.call(arguments); + + if (typeof o === 'object') { + tmp_args[0] = $.extend(o, { + timeOnly: true + }); + } + + return $(this).each(function () { + $.fn.datetimepicker.apply($(this), tmp_args); + }); + }, + + /* + * extend timepicker to datepicker + */ + datetimepicker: function (o) { + o = o || {}; + var tmp_args = arguments; + + if (typeof(o) === 'string') { + if (o === 'getDate' || (o === 'option' && tmp_args.length === 2 && typeof (tmp_args[1]) === 'string')) { + return $.fn.datepicker.apply($(this[0]), tmp_args); + } else { + return this.each(function () { + var $t = $(this); + $t.datepicker.apply($t, tmp_args); + }); + } + } else { + return this.each(function () { + var $t = $(this); + $t.datepicker($.timepicker._newInst($t, o)._defaults); + }); + } + } + }); + + /* + * Public Utility to parse date and time + */ + $.datepicker.parseDateTime = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { + var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings); + if (parseRes.timeObj) { + var t = parseRes.timeObj; + parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec); + parseRes.date.setMicroseconds(t.microsec); + } + + return parseRes.date; + }; + + /* + * Public utility to parse time + */ + $.datepicker.parseTime = function (timeFormat, timeString, options) { + var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}), + iso8601 = (timeFormat.replace(/\'.*?\'/g, '').indexOf('Z') !== -1); + + // Strict parse requires the timeString to match the timeFormat exactly + var strictParse = function (f, s, o) { + + // pattern for standard and localized AM/PM markers + var getPatternAmpm = function (amNames, pmNames) { + var markers = []; + if (amNames) { + $.merge(markers, amNames); + } + if (pmNames) { + $.merge(markers, pmNames); + } + markers = $.map(markers, function (val) { + return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); + }); + return '(' + markers.join('|') + ')?'; + }; + + // figure out position of time elements.. cause js cant do named captures + var getFormatPositions = function (timeFormat) { + var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g), + orders = { + h: -1, + m: -1, + s: -1, + l: -1, + c: -1, + t: -1, + z: -1 + }; + + if (finds) { + for (var i = 0; i < finds.length; i++) { + if (orders[finds[i].toString().charAt(0)] === -1) { + orders[finds[i].toString().charAt(0)] = i + 1; + } + } + } + return orders; + }; + + var regstr = '^' + f.toString() + .replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { + var ml = match.length; + switch (match.charAt(0).toLowerCase()) { + case 'h': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 'm': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 's': + return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})'; + case 'l': + return '(\\d?\\d?\\d)'; + case 'c': + return '(\\d?\\d?\\d)'; + case 'z': + return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?'; + case 't': + return getPatternAmpm(o.amNames, o.pmNames); + default: // literal escaped in quotes + return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?'; + } + }) + .replace(/\s/g, '\\s?') + + o.timeSuffix + '$', + order = getFormatPositions(f), + ampm = '', + treg; + + treg = s.match(new RegExp(regstr, 'i')); + + var resTime = { + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0 + }; + + if (treg) { + if (order.t !== -1) { + if (treg[order.t] === undefined || treg[order.t].length === 0) { + ampm = ''; + resTime.ampm = ''; + } else { + ampm = $.inArray(treg[order.t].toUpperCase(), $.map(o.amNames, function (x,i) { return x.toUpperCase(); })) !== -1 ? 'AM' : 'PM'; + resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0]; + } + } + + if (order.h !== -1) { + if (ampm === 'AM' && treg[order.h] === '12') { + resTime.hour = 0; // 12am = 0 hour + } else { + if (ampm === 'PM' && treg[order.h] !== '12') { + resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12 + } else { + resTime.hour = Number(treg[order.h]); + } + } + } + + if (order.m !== -1) { + resTime.minute = Number(treg[order.m]); + } + if (order.s !== -1) { + resTime.second = Number(treg[order.s]); + } + if (order.l !== -1) { + resTime.millisec = Number(treg[order.l]); + } + if (order.c !== -1) { + resTime.microsec = Number(treg[order.c]); + } + if (order.z !== -1 && treg[order.z] !== undefined) { + resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]); + } + + + return resTime; + } + return false; + };// end strictParse + + // First try JS Date, if that fails, use strictParse + var looseParse = function (f, s, o) { + try { + var d = new Date('2012-01-01 ' + s); + if (isNaN(d.getTime())) { + d = new Date('2012-01-01T' + s); + if (isNaN(d.getTime())) { + d = new Date('01/01/2012 ' + s); + if (isNaN(d.getTime())) { + throw "Unable to parse time with native Date: " + s; + } + } + } + + return { + hour: d.getHours(), + minute: d.getMinutes(), + second: d.getSeconds(), + millisec: d.getMilliseconds(), + microsec: d.getMicroseconds(), + timezone: d.getTimezoneOffset() * -1 + }; + } + catch (err) { + try { + return strictParse(f, s, o); + } + catch (err2) { + $.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f); + } + } + return false; + }; // end looseParse + + if (typeof o.parse === "function") { + return o.parse(timeFormat, timeString, o); + } + if (o.parse === 'loose') { + return looseParse(timeFormat, timeString, o); + } + return strictParse(timeFormat, timeString, o); + }; + + /** + * Public utility to format the time + * @param {string} format format of the time + * @param {Object} time Object not a Date for timezones + * @param {Object} [options] essentially the regional[].. amNames, pmNames, ampm + * @returns {string} the formatted time + */ + $.datepicker.formatTime = function (format, time, options) { + options = options || {}; + options = $.extend({}, $.timepicker._defaults, options); + time = $.extend({ + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null + }, time); + + var tmptime = format, + ampmName = options.amNames[0], + hour = parseInt(time.hour, 10); + + if (hour > 11) { + ampmName = options.pmNames[0]; + } + + tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) { + switch (match) { + case 'HH': + return ('0' + hour).slice(-2); + case 'H': + return hour; + case 'hh': + return ('0' + convert24to12(hour)).slice(-2); + case 'h': + return convert24to12(hour); + case 'mm': + return ('0' + time.minute).slice(-2); + case 'm': + return time.minute; + case 'ss': + return ('0' + time.second).slice(-2); + case 's': + return time.second; + case 'l': + return ('00' + time.millisec).slice(-3); + case 'c': + return ('00' + time.microsec).slice(-3); + case 'z': + return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, false); + case 'Z': + return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, true); + case 'T': + return ampmName.charAt(0).toUpperCase(); + case 'TT': + return ampmName.toUpperCase(); + case 't': + return ampmName.charAt(0).toLowerCase(); + case 'tt': + return ampmName.toLowerCase(); + default: + return match.replace(/'/g, ""); + } + }); + + return tmptime; + }; + + /* + * the bad hack :/ override datepicker so it doesn't close on select + // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 + */ + $.datepicker._base_selectDate = $.datepicker._selectDate; + $.datepicker._selectDate = function (id, dateStr) { + var inst = this._getInst($(id)[0]), + tp_inst = this._get(inst, 'timepicker'), + was_inline; + + if (tp_inst && inst.settings.showTimepicker) { + tp_inst._limitMinMaxDateTime(inst, true); + was_inline = inst.inline; + inst.inline = inst.stay_open = true; + //This way the onSelect handler called from calendarpicker get the full dateTime + this._base_selectDate(id, dateStr); + inst.inline = was_inline; + inst.stay_open = false; + this._notifyChange(inst); + this._updateDatepicker(inst); + } else { + this._base_selectDate(id, dateStr); + } + }; + + /* + * second bad hack :/ override datepicker so it triggers an event when changing the input field + * and does not redraw the datepicker on every selectDate event + */ + $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; + $.datepicker._updateDatepicker = function (inst) { + + // don't popup the datepicker if there is another instance already opened + var input = inst.input[0]; + if ($.datepicker._curInst && $.datepicker._curInst !== inst && $.datepicker._datepickerShowing && $.datepicker._lastInput !== input) { + return; + } + + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { + + this._base_updateDatepicker(inst); + + // Reload the time control when changing something in the input text field. + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + tp_inst._addTimePicker(inst); + } + } + }; + + /* + * third bad hack :/ override datepicker so it allows spaces and colon in the input field + */ + $.datepicker._base_doKeyPress = $.datepicker._doKeyPress; + $.datepicker._doKeyPress = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if ($.datepicker._get(inst, 'constrainInput')) { + var ampm = tp_inst.support.ampm, + tz = tp_inst._defaults.showTimezone !== null ? tp_inst._defaults.showTimezone : tp_inst.support.timezone, + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), + datetimeChars = tp_inst._defaults.timeFormat.toString() + .replace(/[hms]/g, '') + .replace(/TT/g, ampm ? 'APM' : '') + .replace(/Tt/g, ampm ? 'AaPpMm' : '') + .replace(/tT/g, ampm ? 'AaPpMm' : '') + .replace(/T/g, ampm ? 'AP' : '') + .replace(/tt/g, ampm ? 'apm' : '') + .replace(/t/g, ampm ? 'ap' : '') + + " " + tp_inst._defaults.separator + + tp_inst._defaults.timeSuffix + + (tz ? tp_inst._defaults.timezoneList.join('') : '') + + (tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) + + dateChars, + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); + } + } + + return $.datepicker._base_doKeyPress(event); + }; + + /* + * Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField + * Update any alternate field to synchronise with the main field. + */ + $.datepicker._base_updateAlternate = $.datepicker._updateAlternate; + $.datepicker._updateAlternate = function (inst) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var altField = tp_inst._defaults.altField; + if (altField) { // update alternate field too + var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat, + date = this._getDate(inst), + formatCfg = $.datepicker._getFormatConfig(inst), + altFormattedDateTime = '', + altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, + altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix, + altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat; + + altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix; + if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) { + if (tp_inst._defaults.altFormat) { + altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime; + } + else { + altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime; + } + } + $(altField).val( inst.input.val() ? altFormattedDateTime : ""); + } + } + else { + $.datepicker._base_updateAlternate(inst); + } + }; + + /* + * Override key up event to sync manual input changes. + */ + $.datepicker._base_doKeyUp = $.datepicker._doKeyUp; + $.datepicker._doKeyUp = function (event) { + var inst = $.datepicker._getInst(event.target), + tp_inst = $.datepicker._get(inst, 'timepicker'); + + if (tp_inst) { + if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) { + try { + $.datepicker._updateDatepicker(inst); + } catch (err) { + $.timepicker.log(err); + } + } + } + + return $.datepicker._base_doKeyUp(event); + }; + + /* + * override "Today" button to also grab the time and set it to input field. + */ + $.datepicker._base_gotoToday = $.datepicker._gotoToday; + $.datepicker._gotoToday = function (id) { + var inst = this._getInst($(id)[0]); + this._base_gotoToday(id); + var tp_inst = this._get(inst, 'timepicker'); + if (!tp_inst) { + return; + } + + var tzoffset = $.timepicker.timezoneOffsetNumber(tp_inst.timezone); + var now = new Date(); + now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + parseInt(tzoffset, 10)); + this._setTime(inst, now); + this._setDate(inst, now); + tp_inst._onSelectHandler(); + }; + + /* + * Disable & enable the Time in the datetimepicker + */ + $.datepicker._disableTimepickerDatepicker = function (target) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + inst.settings.showTimepicker = false; + tp_inst._defaults.showTimepicker = false; + tp_inst._updateDateTime(inst); + } + }; + + $.datepicker._enableTimepickerDatepicker = function (target) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] + if (tp_inst) { + inst.settings.showTimepicker = true; + tp_inst._defaults.showTimepicker = true; + tp_inst._addTimePicker(inst); // Could be disabled on page load + tp_inst._updateDateTime(inst); + } + }; + + /* + * Create our own set time function + */ + $.datepicker._setTime = function (inst, date) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var defaults = tp_inst._defaults; + + // calling _setTime with no date sets time to defaults + tp_inst.hour = date ? date.getHours() : defaults.hour; + tp_inst.minute = date ? date.getMinutes() : defaults.minute; + tp_inst.second = date ? date.getSeconds() : defaults.second; + tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec; + tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec; + + //check if within min/max times.. + tp_inst._limitMinMaxDateTime(inst, true); + + tp_inst._onTimeChange(); + tp_inst._updateDateTime(inst); + } + }; + + /* + * Create new public method to set only time, callable as $().datepicker('setTime', date) + */ + $.datepicker._setTimeDatepicker = function (target, date, withDate) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + this._setDateFromField(inst); + var tp_date; + if (date) { + if (typeof date === "string") { + tp_inst._parseTime(date, withDate); + tp_date = new Date(); + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + tp_date.setMicroseconds(tp_inst.microsec); + } else { + tp_date = new Date(date.getTime()); + tp_date.setMicroseconds(date.getMicroseconds()); + } + if (tp_date.toString() === 'Invalid Date') { + tp_date = undefined; + } + this._setTime(inst, tp_date); + } + } + + }; + + /* + * override setDate() to allow setting time too within Date object + */ + $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; + $.datepicker._setDateDatepicker = function (target, _date) { + var inst = this._getInst(target); + var date = _date; + if (!inst) { + return; + } + + if (typeof(_date) === 'string') { + date = new Date(_date); + if (!date.getTime()) { + this._base_setDateDatepicker.apply(this, arguments); + date = $(target).datepicker('getDate'); + } + } + + var tp_inst = this._get(inst, 'timepicker'); + var tp_date; + if (date instanceof Date) { + tp_date = new Date(date.getTime()); + tp_date.setMicroseconds(date.getMicroseconds()); + } else { + tp_date = date; + } + + // This is important if you are using the timezone option, javascript's Date + // object will only return the timezone offset for the current locale, so we + // adjust it accordingly. If not using timezone option this won't matter.. + // If a timezone is different in tp, keep the timezone as is + if (tp_inst && tp_date) { + // look out for DST if tz wasn't specified + if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { + tp_inst.timezone = tp_date.getTimezoneOffset() * -1; + } + date = $.timepicker.timezoneAdjust(date, $.timepicker.timezoneOffsetString(-date.getTimezoneOffset()), tp_inst.timezone); + tp_date = $.timepicker.timezoneAdjust(tp_date, $.timepicker.timezoneOffsetString(-tp_date.getTimezoneOffset()), tp_inst.timezone); + } + + this._updateDatepicker(inst); + this._base_setDateDatepicker.apply(this, arguments); + this._setTimeDatepicker(target, tp_date, true); + }; + + /* + * override getDate() to allow getting time too within Date object + */ + $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; + $.datepicker._getDateDatepicker = function (target, noDefault) { + var inst = this._getInst(target); + if (!inst) { + return; + } + + var tp_inst = this._get(inst, 'timepicker'); + + if (tp_inst) { + // if it hasn't yet been defined, grab from field + if (inst.lastVal === undefined) { + this._setDateFromField(inst, noDefault); + } + + var date = this._getDate(inst); + + var currDT = null; + + if (tp_inst.$altInput && tp_inst._defaults.altFieldTimeOnly) { + currDT = tp_inst.$input.val() + ' ' + tp_inst.$altInput.val(); + } + else if (tp_inst.$input.get(0).tagName !== 'INPUT' && tp_inst.$altInput) { + /** + * in case the datetimepicker has been applied to a non-input tag for inline UI, + * and the user has not configured the plugin to display only time in altInput, + * pick current date time from the altInput (and hope for the best, for now, until "ER1" is applied) + * + * @todo ER1. Since altInput can have a totally difference format, convert it to standard format by reading input format from "altFormat" and "altTimeFormat" option values + */ + currDT = tp_inst.$altInput.val(); + } + else { + currDT = tp_inst.$input.val(); + } + + if (date && tp_inst._parseTime(currDT, !inst.settings.timeOnly)) { + date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); + date.setMicroseconds(tp_inst.microsec); + + // This is important if you are using the timezone option, javascript's Date + // object will only return the timezone offset for the current locale, so we + // adjust it accordingly. If not using timezone option this won't matter.. + if (tp_inst.timezone != null) { + // look out for DST if tz wasn't specified + if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { + tp_inst.timezone = date.getTimezoneOffset() * -1; + } + date = $.timepicker.timezoneAdjust(date, tp_inst.timezone, $.timepicker.timezoneOffsetString(-date.getTimezoneOffset())); + } + } + return date; + } + return this._base_getDateDatepicker(target, noDefault); + }; + + /* + * override parseDate() because UI 1.8.14 throws an error about "Extra characters" + * An option in datapicker to ignore extra format characters would be nicer. + */ + $.datepicker._base_parseDate = $.datepicker.parseDate; + $.datepicker.parseDate = function (format, value, settings) { + var date; + try { + date = this._base_parseDate(format, value, settings); + } catch (err) { + // Hack! The error message ends with a colon, a space, and + // the "extra" characters. We rely on that instead of + // attempting to perfectly reproduce the parsing algorithm. + if (err.indexOf(":") >= 0) { + date = this._base_parseDate(format, value.substring(0, value.length - (err.length - err.indexOf(':') - 2)), settings); + $.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format); + } else { + throw err; + } + } + return date; + }; + + /* + * override formatDate to set date with time to the input + */ + $.datepicker._base_formatDate = $.datepicker._formatDate; + $.datepicker._formatDate = function (inst, day, month, year) { + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + tp_inst._updateDateTime(inst); + return tp_inst.$input.val(); + } + return this._base_formatDate(inst); + }; + + /* + * override options setter to add time to maxDate(Time) and minDate(Time). MaxDate + */ + $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; + $.datepicker._optionDatepicker = function (target, name, value) { + var inst = this._getInst(target), + name_clone; + if (!inst) { + return null; + } + + var tp_inst = this._get(inst, 'timepicker'); + if (tp_inst) { + var min = null, + max = null, + onselect = null, + overrides = tp_inst._defaults.evnts, + fns = {}, + prop, + ret, + oldVal, + $target; + if (typeof name === 'string') { // if min/max was set with the string + if (name === 'minDate' || name === 'minDateTime') { + min = value; + } else if (name === 'maxDate' || name === 'maxDateTime') { + max = value; + } else if (name === 'onSelect') { + onselect = value; + } else if (overrides.hasOwnProperty(name)) { + if (typeof (value) === 'undefined') { + return overrides[name]; + } + fns[name] = value; + name_clone = {}; //empty results in exiting function after overrides updated + } + } else if (typeof name === 'object') { //if min/max was set with the JSON + if (name.minDate) { + min = name.minDate; + } else if (name.minDateTime) { + min = name.minDateTime; + } else if (name.maxDate) { + max = name.maxDate; + } else if (name.maxDateTime) { + max = name.maxDateTime; + } + for (prop in overrides) { + if (overrides.hasOwnProperty(prop) && name[prop]) { + fns[prop] = name[prop]; + } + } + } + for (prop in fns) { + if (fns.hasOwnProperty(prop)) { + overrides[prop] = fns[prop]; + if (!name_clone) { name_clone = $.extend({}, name); } + delete name_clone[prop]; + } + } + if (name_clone && isEmptyObject(name_clone)) { return; } + if (min) { //if min was set + if (min === 0) { + min = new Date(); + } else { + min = new Date(min); + } + tp_inst._defaults.minDate = min; + tp_inst._defaults.minDateTime = min; + } else if (max) { //if max was set + if (max === 0) { + max = new Date(); + } else { + max = new Date(max); + } + tp_inst._defaults.maxDate = max; + tp_inst._defaults.maxDateTime = max; + } else if (onselect) { + tp_inst._defaults.onSelect = onselect; + } + + // Datepicker will override our date when we call _base_optionDatepicker when + // calling minDate/maxDate, so we will first grab the value, call + // _base_optionDatepicker, then set our value back. + if(min || max){ + $target = $(target); + oldVal = $target.datetimepicker('getDate'); + ret = this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value); + $target.datetimepicker('setDate', oldVal); + return ret; + } + } + if (value === undefined) { + return this._base_optionDatepicker.call($.datepicker, target, name); + } + return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value); + }; + + /* + * jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype, + * it will return false for all objects + */ + var isEmptyObject = function (obj) { + var prop; + for (prop in obj) { + if (obj.hasOwnProperty(prop)) { + return false; + } + } + return true; + }; + + /* + * jQuery extend now ignores nulls! + */ + var extendRemove = function (target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] === null || props[name] === undefined) { + target[name] = props[name]; + } + } + return target; + }; + + /* + * Determine by the time format which units are supported + * Returns an object of booleans for each unit + */ + var detectSupport = function (timeFormat) { + var tf = timeFormat.replace(/'.*?'/g, '').toLowerCase(), // removes literals + isIn = function (f, t) { // does the format contain the token? + return f.indexOf(t) !== -1 ? true : false; + }; + return { + hour: isIn(tf, 'h'), + minute: isIn(tf, 'm'), + second: isIn(tf, 's'), + millisec: isIn(tf, 'l'), + microsec: isIn(tf, 'c'), + timezone: isIn(tf, 'z'), + ampm: isIn(tf, 't') && isIn(timeFormat, 'h'), + iso8601: isIn(timeFormat, 'Z') + }; + }; + + /* + * Converts 24 hour format into 12 hour + * Returns 12 hour without leading 0 + */ + var convert24to12 = function (hour) { + hour %= 12; + + if (hour === 0) { + hour = 12; + } + + return String(hour); + }; + + var computeEffectiveSetting = function (settings, property) { + return settings && settings[property] ? settings[property] : $.timepicker._defaults[property]; + }; + + /* + * Splits datetime string into date and time substrings. + * Throws exception when date can't be parsed + * Returns {dateString: dateString, timeString: timeString} + */ + var splitDateTime = function (dateTimeString, timeSettings) { + // The idea is to get the number separator occurrences in datetime and the time format requested (since time has + // fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split. + var separator = computeEffectiveSetting(timeSettings, 'separator'), + format = computeEffectiveSetting(timeSettings, 'timeFormat'), + timeParts = format.split(separator), // how many occurrences of separator may be in our format? + timePartsLen = timeParts.length, + allParts = dateTimeString.split(separator), + allPartsLen = allParts.length; + + if (allPartsLen > 1) { + return { + dateString: allParts.splice(0, allPartsLen - timePartsLen).join(separator), + timeString: allParts.splice(0, timePartsLen).join(separator) + }; + } + + return { + dateString: dateTimeString, + timeString: '' + }; + }; + + /* + * Internal function to parse datetime interval + * Returns: {date: Date, timeObj: Object}, where + * date - parsed date without time (type Date) + * timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional + */ + var parseDateTimeInternal = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { + var date, + parts, + parsedTime; + + parts = splitDateTime(dateTimeString, timeSettings); + date = $.datepicker._base_parseDate(dateFormat, parts.dateString, dateSettings); + + if (parts.timeString === '') { + return { + date: date + }; + } + + parsedTime = $.datepicker.parseTime(timeFormat, parts.timeString, timeSettings); + + if (!parsedTime) { + throw 'Wrong time format'; + } + + return { + date: date, + timeObj: parsedTime + }; + }; + + /* + * Internal function to set timezone_select to the local timezone + */ + var selectLocalTimezone = function (tp_inst, date) { + if (tp_inst && tp_inst.timezone_select) { + var now = date || new Date(); + tp_inst.timezone_select.val(-now.getTimezoneOffset()); + } + }; + + /* + * Create a Singleton Instance + */ + $.timepicker = new Timepicker(); + + /** + * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5) + * @param {number} tzMinutes if not a number, less than -720 (-1200), or greater than 840 (+1400) this value is returned + * @param {boolean} iso8601 if true formats in accordance to iso8601 "+12:45" + * @return {string} + */ + $.timepicker.timezoneOffsetString = function (tzMinutes, iso8601) { + if (isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720) { + return tzMinutes; + } + + var off = tzMinutes, + minutes = off % 60, + hours = (off - minutes) / 60, + iso = iso8601 ? ':' : '', + tz = (off >= 0 ? '+' : '-') + ('0' + Math.abs(hours)).slice(-2) + iso + ('0' + Math.abs(minutes)).slice(-2); + + if (tz === '+00:00') { + return 'Z'; + } + return tz; + }; + + /** + * Get the number in minutes that represents a timezone string + * @param {string} tzString formatted like "+0500", "-1245", "Z" + * @return {number} the offset minutes or the original string if it doesn't match expectations + */ + $.timepicker.timezoneOffsetNumber = function (tzString) { + var normalized = tzString.toString().replace(':', ''); // excuse any iso8601, end up with "+1245" + + if (normalized.toUpperCase() === 'Z') { // if iso8601 with Z, its 0 minute offset + return 0; + } + + if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back + return parseInt(tzString, 10); + } + + return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus + ((parseInt(normalized.substr(1, 2), 10) * 60) + // hours (converted to minutes) + parseInt(normalized.substr(3, 2), 10))); // minutes + }; + + /** + * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate) + * @param {Date} date + * @param {string} fromTimezone formatted like "+0500", "-1245" + * @param {string} toTimezone formatted like "+0500", "-1245" + * @return {Date} + */ + $.timepicker.timezoneAdjust = function (date, fromTimezone, toTimezone) { + var fromTz = $.timepicker.timezoneOffsetNumber(fromTimezone); + var toTz = $.timepicker.timezoneOffsetNumber(toTimezone); + if (!isNaN(toTz)) { + date.setMinutes(date.getMinutes() + (-fromTz) - (-toTz)); + } + return date; + }; + + /** + * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * n.b. The input value must be correctly formatted (reformatting is not supported) + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the timepicker() call + * @return {jQuery} + */ + $.timepicker.timeRange = function (startTime, endTime, options) { + return $.timepicker.handleRange('timepicker', startTime, endTime, options); + }; + + /** + * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @param {string} method Can be used to specify the type of picker to be added + * @return {jQuery} + */ + $.timepicker.datetimeRange = function (startTime, endTime, options) { + $.timepicker.handleRange('datetimepicker', startTime, endTime, options); + }; + + /** + * Calls `datepicker` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @return {jQuery} + */ + $.timepicker.dateRange = function (startTime, endTime, options) { + $.timepicker.handleRange('datepicker', startTime, endTime, options); + }; + + /** + * Calls `method` on the `startTime` and `endTime` elements, and configures them to + * enforce date range limits. + * @param {string} method Can be used to specify the type of picker to be added + * @param {Element} startTime + * @param {Element} endTime + * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`, + * a boolean value that can be used to reformat the input values to the `dateFormat`. + * @return {jQuery} + */ + $.timepicker.handleRange = function (method, startTime, endTime, options) { + options = $.extend({}, { + minInterval: 0, // min allowed interval in milliseconds + maxInterval: 0, // max allowed interval in milliseconds + start: {}, // options for start picker + end: {} // options for end picker + }, options); + + // for the mean time this fixes an issue with calling getDate with timepicker() + var timeOnly = false; + if(method === 'timepicker'){ + timeOnly = true; + method = 'datetimepicker'; + } + + function checkDates(changed, other) { + var startdt = startTime[method]('getDate'), + enddt = endTime[method]('getDate'), + changeddt = changed[method]('getDate'); + + if (startdt !== null) { + var minDate = new Date(startdt.getTime()), + maxDate = new Date(startdt.getTime()); + + minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval); + maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval); + + if (options.minInterval > 0 && minDate > enddt) { // minInterval check + endTime[method]('setDate', minDate); + } + else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check + endTime[method]('setDate', maxDate); + } + else if (startdt > enddt) { + other[method]('setDate', changeddt); + } + } + } + + function selected(changed, other, option) { + if (!changed.val()) { + return; + } + var date = changed[method].call(changed, 'getDate'); + if (date !== null && options.minInterval > 0) { + if (option === 'minDate') { + date.setMilliseconds(date.getMilliseconds() + options.minInterval); + } + if (option === 'maxDate') { + date.setMilliseconds(date.getMilliseconds() - options.minInterval); + } + } + + if (date.getTime) { + other[method].call(other, 'option', option, date); + } + } + + $.fn[method].call(startTime, $.extend({ + timeOnly: timeOnly, + onClose: function (dateText, inst) { + checkDates($(this), endTime); + }, + onSelect: function (selectedDateTime) { + selected($(this), endTime, 'minDate'); + } + }, options, options.start)); + $.fn[method].call(endTime, $.extend({ + timeOnly: timeOnly, + onClose: function (dateText, inst) { + checkDates($(this), startTime); + }, + onSelect: function (selectedDateTime) { + selected($(this), startTime, 'maxDate'); + } + }, options, options.end)); + + checkDates(startTime, endTime); + + selected(startTime, endTime, 'minDate'); + selected(endTime, startTime, 'maxDate'); + + return $([startTime.get(0), endTime.get(0)]); + }; + + /** + * Log error or data to the console during error or debugging + * @param {Object} err pass any type object to log to the console during error or debugging + * @return {void} + */ + $.timepicker.log = function () { + // Older IE (9, maybe 10) throw error on accessing `window.console.log.apply`, so check first. + if (window.console && window.console.log && window.console.log.apply) { + window.console.log.apply(window.console, Array.prototype.slice.call(arguments)); + } + }; + + /* + * Add util object to allow access to private methods for testability. + */ + $.timepicker._util = { + _extendRemove: extendRemove, + _isEmptyObject: isEmptyObject, + _convert24to12: convert24to12, + _detectSupport: detectSupport, + _selectLocalTimezone: selectLocalTimezone, + _computeEffectiveSetting: computeEffectiveSetting, + _splitDateTime: splitDateTime, + _parseDateTimeInternal: parseDateTimeInternal + }; + + /* + * Microsecond support + */ + if (!Date.prototype.getMicroseconds) { + Date.prototype.microseconds = 0; + Date.prototype.getMicroseconds = function () { return this.microseconds; }; + Date.prototype.setMicroseconds = function (m) { + this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000)); + this.microseconds = m % 1000; + return this; + }; + } + + /* + * Keep up with the version + */ + $.timepicker.version = "1.6.3"; + +})); diff --git a/lib/web/jquery/jquery-ui.js b/lib/web/jquery/jquery-ui.js index 2108724d7e9a5..b053cc47942d3 100644 --- a/lib/web/jquery/jquery-ui.js +++ b/lib/web/jquery/jquery-ui.js @@ -1,27 +1,30 @@ -/*! jQuery UI - v1.12.1 - 2021-09-16 +/*! jQuery UI - v1.13.0 - 2021-11-29 * http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js +* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { +( function( factory ) { + "use strict"; - // AMD. Register as an anonymous module. - define([ "jquery" ], factory ); - } else { + if ( typeof define === "function" && define.amd ) { - // Browser globals - factory( jQuery ); - } -}(function( $ ) { + // AMD. Register as an anonymous module. + define( [ "jquery" ], factory ); + } else { -$.ui = $.ui || {}; + // Browser globals + factory( jQuery ); + } +} )( function( $ ) { + "use strict"; -var version = $.ui.version = "1.12.1"; + $.ui = $.ui || {}; + var version = $.ui.version = "1.13.0"; -/*! - * jQuery UI Widget 1.12.1 + + /*! + * jQuery UI Widget 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -36,715 +39,730 @@ var version = $.ui.version = "1.12.1"; //>>demos: http://jqueryui.com/widget/ - -var widgetUuid = 0; -var widgetSlice = Array.prototype.slice; - -$.cleanData = ( function( orig ) { - return function( elems ) { - var events, elem, i; - for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { - try { - - // Only trigger remove when necessary to save time - events = $._data( elem, "events" ); - if ( events && events.remove ) { - $( elem ).triggerHandler( "remove" ); - } - - // Http://bugs.jquery.com/ticket/8235 - } catch ( e ) {} - } - orig( elems ); - }; -} )( $.cleanData ); - -$.widget = function( name, base, prototype ) { - var existingConstructor, constructor, basePrototype; - - // ProxiedPrototype allows the provided prototype to remain unmodified - // so that it can be used as a mixin for multiple widgets (#8876) - var proxiedPrototype = {}; - - var namespace = name.split( "." )[ 0 ]; - name = name.split( "." )[ 1 ]; - var fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - if ( $.isArray( prototype ) ) { - prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); - } - - // Create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { - return !!$.data( elem, fullName ); - }; - - $[ namespace ] = $[ namespace ] || {}; - existingConstructor = $[ namespace ][ name ]; - constructor = $[ namespace ][ name ] = function( options, element ) { - - // Allow instantiation without "new" keyword - if ( !this._createWidget ) { - return new constructor( options, element ); - } - - // Allow instantiation without initializing for simple inheritance - // must use "new" keyword (the code above always passes args) - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - - // Extend with the existing constructor to carry over any static properties - $.extend( constructor, existingConstructor, { - version: prototype.version, - - // Copy the object used to create the prototype in case we need to - // redefine the widget later - _proto: $.extend( {}, prototype ), - - // Track widgets that inherit from this widget in case this widget is - // redefined after a widget inherits from it - _childConstructors: [] - } ); - - basePrototype = new base(); - - // We need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from - basePrototype.options = $.widget.extend( {}, basePrototype.options ); - $.each( prototype, function( prop, value ) { - if ( !$.isFunction( value ) ) { - proxiedPrototype[ prop ] = value; - return; - } - proxiedPrototype[ prop ] = ( function() { - function _super() { - return base.prototype[ prop ].apply( this, arguments ); - } - - function _superApply( args ) { - return base.prototype[ prop ].apply( this, args ); - } - - return function() { - var __super = this._super; - var __superApply = this._superApply; - var returnValue; - - this._super = _super; - this._superApply = _superApply; - - returnValue = value.apply( this, arguments ); - - this._super = __super; - this._superApply = __superApply; - - return returnValue; - }; - } )(); - } ); - constructor.prototype = $.widget.extend( basePrototype, { - - // TODO: remove support for widgetEventPrefix - // always use the name + a colon as the prefix, e.g., draggable:start - // don't prefix for widgets that aren't DOM-based - widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name - }, proxiedPrototype, { - constructor: constructor, - namespace: namespace, - widgetName: name, - widgetFullName: fullName - } ); - - // If this widget is being redefined then we need to find all widgets that - // are inheriting from it and redefine all of them so that they inherit from - // the new version of this widget. We're essentially trying to replace one - // level in the prototype chain. - if ( existingConstructor ) { - $.each( existingConstructor._childConstructors, function( i, child ) { - var childPrototype = child.prototype; - - // Redefine the child widget using the same prototype that was - // originally used, but inherit from the new version of the base - $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, - child._proto ); - } ); - - // Remove the list of existing child constructors from the old constructor - // so the old child constructors can be garbage collected - delete existingConstructor._childConstructors; - } else { - base._childConstructors.push( constructor ); - } - - $.widget.bridge( name, constructor ); - - return constructor; -}; - -$.widget.extend = function( target ) { - var input = widgetSlice.call( arguments, 1 ); - var inputIndex = 0; - var inputLength = input.length; - var key; - var value; - - for ( ; inputIndex < inputLength; inputIndex++ ) { - for ( key in input[ inputIndex ] ) { - value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { - - // Clone objects - if ( $.isPlainObject( value ) ) { - target[ key ] = $.isPlainObject( target[ key ] ) ? - $.widget.extend( {}, target[ key ], value ) : - - // Don't extend strings, arrays, etc. with objects - $.widget.extend( {}, value ); - - // Copy everything else by reference - } else { - target[ key ] = value; - } - } - } - } - return target; -}; - -$.widget.bridge = function( name, object ) { - var fullName = object.prototype.widgetFullName || name; - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string"; - var args = widgetSlice.call( arguments, 1 ); - var returnValue = this; - - if ( isMethodCall ) { - - // If this is an empty collection, we need to have the instance method - // return undefined instead of the jQuery instance - if ( !this.length && options === "instance" ) { - returnValue = undefined; - } else { - this.each( function() { - var methodValue; - var instance = $.data( this, fullName ); - - if ( options === "instance" ) { - returnValue = instance; - return false; - } - - if ( !instance ) { - return $.error( "cannot call methods on " + name + - " prior to initialization; " + - "attempted to call method '" + options + "'" ); - } - - if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { - return $.error( "no such method '" + options + "' for " + name + - " widget instance" ); - } - - methodValue = instance[ options ].apply( instance, args ); - - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue && methodValue.jquery ? - returnValue.pushStack( methodValue.get() ) : - methodValue; - return false; - } - } ); - } - } else { - - // Allow multiple hashes to be passed on init - if ( args.length ) { - options = $.widget.extend.apply( null, [ options ].concat( args ) ); - } - - this.each( function() { - var instance = $.data( this, fullName ); - if ( instance ) { - instance.option( options || {} ); - if ( instance._init ) { - instance._init(); - } - } else { - $.data( this, fullName, new object( options, this ) ); - } - } ); - } - - return returnValue; - }; -}; - -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - defaultElement: "<div>", - - options: { - classes: {}, - disabled: false, - - // Callbacks - create: null - }, - - _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; - this.element = $( element ); - this.uuid = widgetUuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - this.classesElementLookup = {}; - - if ( element !== this ) { - $.data( element, this.widgetFullName, this ); - this._on( true, this.element, { - remove: function( event ) { - if ( event.target === element ) { - this.destroy(); - } - } - } ); - this.document = $( element.style ? - - // Element within the document - element.ownerDocument : - - // Element is window or document - element.document || element ); - this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); - } - - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); - - this._create(); - - if ( this.options.disabled ) { - this._setOptionDisabled( this.options.disabled ); - } - - this._trigger( "create", null, this._getCreateEventData() ); - this._init(); - }, - - _getCreateOptions: function() { - return {}; - }, - - _getCreateEventData: $.noop, - - _create: $.noop, - - _init: $.noop, - - destroy: function() { - var that = this; - - this._destroy(); - $.each( this.classesElementLookup, function( key, value ) { - that._removeClass( value, key ); - } ); - - // We can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() - this.element - .off( this.eventNamespace ) - .removeData( this.widgetFullName ); - this.widget() - .off( this.eventNamespace ) - .removeAttr( "aria-disabled" ); - - // Clean up events and states - this.bindings.off( this.eventNamespace ); - }, - - _destroy: $.noop, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key; - var parts; - var curOption; - var i; - - if ( arguments.length === 0 ) { - - // Don't return a reference to the internal hash - return $.widget.extend( {}, this.options ); - } - - if ( typeof key === "string" ) { - - // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } - options = {}; - parts = key.split( "." ); - key = parts.shift(); - if ( parts.length ) { - curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); - for ( i = 0; i < parts.length - 1; i++ ) { - curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; - curOption = curOption[ parts[ i ] ]; - } - key = parts.pop(); - if ( arguments.length === 1 ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( arguments.length === 1 ) { - return this.options[ key ] === undefined ? null : this.options[ key ]; - } - options[ key ] = value; - } - } - - this._setOptions( options ); - - return this; - }, - - _setOptions: function( options ) { - var key; - - for ( key in options ) { - this._setOption( key, options[ key ] ); - } - - return this; - }, - - _setOption: function( key, value ) { - if ( key === "classes" ) { - this._setOptionClasses( value ); - } - - this.options[ key ] = value; - - if ( key === "disabled" ) { - this._setOptionDisabled( value ); - } - - return this; - }, - - _setOptionClasses: function( value ) { - var classKey, elements, currentElements; - - for ( classKey in value ) { - currentElements = this.classesElementLookup[ classKey ]; - if ( value[ classKey ] === this.options.classes[ classKey ] || - !currentElements || - !currentElements.length ) { - continue; - } - - // We are doing this to create a new jQuery object because the _removeClass() call - // on the next line is going to destroy the reference to the current elements being - // tracked. We need to save a copy of this collection so that we can add the new classes - // below. - elements = $( currentElements.get() ); - this._removeClass( currentElements, classKey ); - - // We don't use _addClass() here, because that uses this.options.classes - // for generating the string of classes. We want to use the value passed in from - // _setOption(), this is the new value of the classes option which was passed to - // _setOption(). We pass this value directly to _classes(). - elements.addClass( this._classes( { - element: elements, - keys: classKey, - classes: value, - add: true - } ) ); - } - }, - - _setOptionDisabled: function( value ) { - this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); - - // If the widget is becoming disabled, then nothing is interactive - if ( value ) { - this._removeClass( this.hoverable, null, "ui-state-hover" ); - this._removeClass( this.focusable, null, "ui-state-focus" ); - } - }, - - enable: function() { - return this._setOptions( { disabled: false } ); - }, - - disable: function() { - return this._setOptions( { disabled: true } ); - }, - - _classes: function( options ) { - var full = []; - var that = this; - - options = $.extend( { - element: this.element, - classes: this.options.classes || {} - }, options ); - - function processClassString( classes, checkOption ) { - var current, i; - for ( i = 0; i < classes.length; i++ ) { - current = that.classesElementLookup[ classes[ i ] ] || $(); - if ( options.add ) { - current = $( $.unique( current.get().concat( options.element.get() ) ) ); - } else { - current = $( current.not( options.element ).get() ); - } - that.classesElementLookup[ classes[ i ] ] = current; - full.push( classes[ i ] ); - if ( checkOption && options.classes[ classes[ i ] ] ) { - full.push( options.classes[ classes[ i ] ] ); - } - } - } - - this._on( options.element, { - "remove": "_untrackClassesElement" - } ); - - if ( options.keys ) { - processClassString( options.keys.match( /\S+/g ) || [], true ); - } - if ( options.extra ) { - processClassString( options.extra.match( /\S+/g ) || [] ); - } - - return full.join( " " ); - }, - - _untrackClassesElement: function( event ) { - var that = this; - $.each( that.classesElementLookup, function( key, value ) { - if ( $.inArray( event.target, value ) !== -1 ) { - that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); - } - } ); - }, - - _removeClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, false ); - }, - - _addClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, true ); - }, - - _toggleClass: function( element, keys, extra, add ) { - add = ( typeof add === "boolean" ) ? add : extra; - var shift = ( typeof element === "string" || element === null ), - options = { - extra: shift ? keys : extra, - keys: shift ? element : keys, - element: shift ? this.element : element, - add: add - }; - options.element.toggleClass( this._classes( options ), add ); - return this; - }, - - _on: function( suppressDisabledCheck, element, handlers ) { - var delegateElement; - var instance = this; - - // No suppressDisabledCheck flag, shuffle arguments - if ( typeof suppressDisabledCheck !== "boolean" ) { - handlers = element; - element = suppressDisabledCheck; - suppressDisabledCheck = false; - } - - // No element argument, shuffle and use this.element - if ( !handlers ) { - handlers = element; - element = this.element; - delegateElement = this.widget(); - } else { - element = delegateElement = $( element ); - this.bindings = this.bindings.add( element ); - } - - $.each( handlers, function( event, handler ) { - function handlerProxy() { - - // Allow widgets to customize the disabled handling - // - disabled as an array instead of boolean - // - disabled class as method for disabling individual parts - if ( !suppressDisabledCheck && - ( instance.options.disabled === true || - $( this ).hasClass( "ui-state-disabled" ) ) ) { - return; - } - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - - // Copy the guid so direct unbinding works - if ( typeof handler !== "string" ) { - handlerProxy.guid = handler.guid = - handler.guid || handlerProxy.guid || $.guid++; - } - - var match = event.match( /^([\w:-]*)\s*(.*)$/ ); - var eventName = match[ 1 ] + instance.eventNamespace; - var selector = match[ 2 ]; - - if ( selector ) { - delegateElement.on( eventName, selector, handlerProxy ); - } else { - element.on( eventName, handlerProxy ); - } - } ); - }, - - _off: function( element, eventName ) { - eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + - this.eventNamespace; - element.off( eventName ).off( eventName ); - - // Clear the stack to avoid memory leaks (#10056) - this.bindings = $( this.bindings.not( element ).get() ); - this.focusable = $( this.focusable.not( element ).get() ); - this.hoverable = $( this.hoverable.not( element ).get() ); - }, - - _delay: function( handler, delay ) { - function handlerProxy() { - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - var instance = this; - return setTimeout( handlerProxy, delay || 0 ); - }, - - _hoverable: function( element ) { - this.hoverable = this.hoverable.add( element ); - this._on( element, { - mouseenter: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); - }, - mouseleave: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); - } - } ); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); - }, - focusout: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); - } - } ); - }, - - _trigger: function( type, event, data ) { - var prop, orig; - var callback = this.options[ type ]; - - data = data || {}; - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - - // The original event may come from any element - // so we need to reset the target on the new event - event.target = this.element[ 0 ]; - - // Copy original event properties over to the new event - orig = event.originalEvent; - if ( orig ) { - for ( prop in orig ) { - if ( !( prop in event ) ) { - event[ prop ] = orig[ prop ]; - } - } - } - - this.element.trigger( event, data ); - return !( $.isFunction( callback ) && - callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || - event.isDefaultPrevented() ); - } -}; - -$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { - $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { - if ( typeof options === "string" ) { - options = { effect: options }; - } - - var hasOptions; - var effectName = !options ? - method : - options === true || typeof options === "number" ? - defaultEffect : - options.effect || defaultEffect; - - options = options || {}; - if ( typeof options === "number" ) { - options = { duration: options }; - } - - hasOptions = !$.isEmptyObject( options ); - options.complete = callback; - - if ( options.delay ) { - element.delay( options.delay ); - } - - if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { - element[ method ]( options ); - } else if ( effectName !== method && element[ effectName ] ) { - element[ effectName ]( options.duration, options.easing, callback ); - } else { - element.queue( function( next ) { - $( this )[ method ](); - if ( callback ) { - callback.call( element[ 0 ] ); - } - next(); - } ); - } - }; -} ); - -var widget = $.widget; - - -/*! - * jQuery UI Position 1.12.1 + var widgetUuid = 0; + var widgetHasOwnProperty = Array.prototype.hasOwnProperty; + var widgetSlice = Array.prototype.slice; + + $.cleanData = ( function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + } + orig( elems ); + }; + } )( $.cleanData ); + + $.widget = function( name, base, prototype ) { + var existingConstructor, constructor, basePrototype; + + // ProxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + var proxiedPrototype = {}; + + var namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; + var fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + if ( Array.isArray( prototype ) ) { + prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); + } + + // Create selector for plugin + $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + + // Allow instantiation without "new" keyword + if ( !this || !this._createWidget ) { + return new constructor( options, element ); + } + + // Allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + // Extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + + // Copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + + // Track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + } ); + + basePrototype = new base(); + + // We need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( typeof value !== "function" ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = ( function() { + function _super() { + return base.prototype[ prop ].apply( this, arguments ); + } + + function _superApply( args ) { + return base.prototype[ prop ].apply( this, args ); + } + + return function() { + var __super = this._super; + var __superApply = this._superApply; + var returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + } )(); + } ); + constructor.prototype = $.widget.extend( basePrototype, { + + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + } ); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // Redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, + child._proto ); + } ); + + // Remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; + }; + + $.widget.extend = function( target ) { + var input = widgetSlice.call( arguments, 1 ); + var inputIndex = 0; + var inputLength = input.length; + var key; + var value; + + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) { + + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; + }; + + $.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string"; + var args = widgetSlice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var instance = $.data( this, fullName ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on " + name + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( typeof instance[ options ] !== "function" || + options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + } ); + } + + return returnValue; + }; + }; + + $.Widget = function( /* options, element */ ) {}; + $.Widget._childConstructors = []; + + $.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "<div>", + + options: { + classes: {}, + disabled: false, + + // Callbacks + create: null + }, + + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widgetUuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + this.classesElementLookup = {}; + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + } ); + this.document = $( element.style ? + + // Element within the document + element.ownerDocument : + + // Element is window or document + element.document || element ); + this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + + if ( this.options.disabled ) { + this._setOptionDisabled( this.options.disabled ); + } + + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + + _getCreateOptions: function() { + return {}; + }, + + _getCreateEventData: $.noop, + + _create: $.noop, + + _init: $.noop, + + destroy: function() { + var that = this; + + this._destroy(); + $.each( this.classesElementLookup, function( key, value ) { + that._removeClass( value, key ); + } ); + + // We can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .off( this.eventNamespace ) + .removeData( this.widgetFullName ); + this.widget() + .off( this.eventNamespace ) + .removeAttr( "aria-disabled" ); + + // Clean up events and states + this.bindings.off( this.eventNamespace ); + }, + + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + var parts; + var curOption; + var i; + + if ( arguments.length === 0 ) { + + // Don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + + // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + + _setOption: function( key, value ) { + if ( key === "classes" ) { + this._setOptionClasses( value ); + } + + this.options[ key ] = value; + + if ( key === "disabled" ) { + this._setOptionDisabled( value ); + } + + return this; + }, + + _setOptionClasses: function( value ) { + var classKey, elements, currentElements; + + for ( classKey in value ) { + currentElements = this.classesElementLookup[ classKey ]; + if ( value[ classKey ] === this.options.classes[ classKey ] || + !currentElements || + !currentElements.length ) { + continue; + } + + // We are doing this to create a new jQuery object because the _removeClass() call + // on the next line is going to destroy the reference to the current elements being + // tracked. We need to save a copy of this collection so that we can add the new classes + // below. + elements = $( currentElements.get() ); + this._removeClass( currentElements, classKey ); + + // We don't use _addClass() here, because that uses this.options.classes + // for generating the string of classes. We want to use the value passed in from + // _setOption(), this is the new value of the classes option which was passed to + // _setOption(). We pass this value directly to _classes(). + elements.addClass( this._classes( { + element: elements, + keys: classKey, + classes: value, + add: true + } ) ); + } + }, + + _setOptionDisabled: function( value ) { + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this._removeClass( this.hoverable, null, "ui-state-hover" ); + this._removeClass( this.focusable, null, "ui-state-focus" ); + } + }, + + enable: function() { + return this._setOptions( { disabled: false } ); + }, + + disable: function() { + return this._setOptions( { disabled: true } ); + }, + + _classes: function( options ) { + var full = []; + var that = this; + + options = $.extend( { + element: this.element, + classes: this.options.classes || {} + }, options ); + + function bindRemoveEvent() { + options.element.each( function( _, element ) { + var isTracked = $.map( that.classesElementLookup, function( elements ) { + return elements; + } ) + .some( function( elements ) { + return elements.is( element ); + } ); + + if ( !isTracked ) { + that._on( $( element ), { + remove: "_untrackClassesElement" + } ); + } + } ); + } + + function processClassString( classes, checkOption ) { + var current, i; + for ( i = 0; i < classes.length; i++ ) { + current = that.classesElementLookup[ classes[ i ] ] || $(); + if ( options.add ) { + bindRemoveEvent(); + current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) ); + } else { + current = $( current.not( options.element ).get() ); + } + that.classesElementLookup[ classes[ i ] ] = current; + full.push( classes[ i ] ); + if ( checkOption && options.classes[ classes[ i ] ] ) { + full.push( options.classes[ classes[ i ] ] ); + } + } + } + + if ( options.keys ) { + processClassString( options.keys.match( /\S+/g ) || [], true ); + } + if ( options.extra ) { + processClassString( options.extra.match( /\S+/g ) || [] ); + } + + return full.join( " " ); + }, + + _untrackClassesElement: function( event ) { + var that = this; + $.each( that.classesElementLookup, function( key, value ) { + if ( $.inArray( event.target, value ) !== -1 ) { + that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); + } + } ); + + this._off( $( event.target ) ); + }, + + _removeClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, false ); + }, + + _addClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, true ); + }, + + _toggleClass: function( element, keys, extra, add ) { + add = ( typeof add === "boolean" ) ? add : extra; + var shift = ( typeof element === "string" || element === null ), + options = { + extra: shift ? keys : extra, + keys: shift ? element : keys, + element: shift ? this.element : element, + add: add + }; + options.element.toggleClass( this._classes( options ), add ); + return this; + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement; + var instance = this; + + // No suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // No element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + + // Allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // Copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ); + var eventName = match[ 1 ] + instance.eventNamespace; + var selector = match[ 2 ]; + + if ( selector ) { + delegateElement.on( eventName, selector, handlerProxy ); + } else { + element.on( eventName, handlerProxy ); + } + } ); + }, + + _off: function( element, eventName ) { + eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.off( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); + }, + mouseleave: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); + } + } ); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); + }, + focusout: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); + } + } ); + }, + + _trigger: function( type, event, data ) { + var prop, orig; + var callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + + // The original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // Copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( typeof callback === "function" && + callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } + }; + + $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + + var hasOptions; + var effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } else if ( options === true ) { + options = {}; + } + + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + + if ( options.delay ) { + element.delay( options.delay ); + } + + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue( function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + } ); + } + }; + } ); + + var widget = $.widget; + + + /*! + * jQuery UI Position 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -761,478 +779,487 @@ var widget = $.widget; //>>demos: http://jqueryui.com/position/ -( function() { -var cachedScrollbarWidth, - max = Math.max, - abs = Math.abs, - rhorizontal = /left|center|right/, - rvertical = /top|center|bottom/, - roffset = /[\+\-]\d+(\.[\d]+)?%?/, - rposition = /^\w+/, - rpercent = /%$/, - _position = $.fn.position; - -function getOffsets( offsets, width, height ) { - return [ - parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), - parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) - ]; -} - -function parseCss( element, property ) { - return parseInt( $.css( element, property ), 10 ) || 0; -} - -function getDimensions( elem ) { - var raw = elem[ 0 ]; - if ( raw.nodeType === 9 ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: 0, left: 0 } - }; - } - if ( $.isWindow( raw ) ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: elem.scrollTop(), left: elem.scrollLeft() } - }; - } - if ( raw.preventDefault ) { - return { - width: 0, - height: 0, - offset: { top: raw.pageY, left: raw.pageX } - }; - } - return { - width: elem.outerWidth(), - height: elem.outerHeight(), - offset: elem.offset() - }; -} - -$.position = { - scrollbarWidth: function() { - if ( cachedScrollbarWidth !== undefined ) { - return cachedScrollbarWidth; - } - var w1, w2, - div = $( "<div " + - "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" + - "<div style='height:100px;width:auto;'></div></div>" ), - innerDiv = div.children()[ 0 ]; - - $( "body" ).append( div ); - w1 = innerDiv.offsetWidth; - div.css( "overflow", "scroll" ); - - w2 = innerDiv.offsetWidth; - - if ( w1 === w2 ) { - w2 = div[ 0 ].clientWidth; - } - - div.remove(); - - return ( cachedScrollbarWidth = w1 - w2 ); - }, - getScrollInfo: function( within ) { - var overflowX = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-x" ), - overflowY = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-y" ), - hasOverflowX = overflowX === "scroll" || - ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), - hasOverflowY = overflowY === "scroll" || - ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); - return { - width: hasOverflowY ? $.position.scrollbarWidth() : 0, - height: hasOverflowX ? $.position.scrollbarWidth() : 0 - }; - }, - getWithinInfo: function( element ) { - var withinElement = $( element || window ), - isWindow = $.isWindow( withinElement[ 0 ] ), - isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, - hasOffset = !isWindow && !isDocument; - return { - element: withinElement, - isWindow: isWindow, - isDocument: isDocument, - offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, - scrollLeft: withinElement.scrollLeft(), - scrollTop: withinElement.scrollTop(), - width: withinElement.outerWidth(), - height: withinElement.outerHeight() - }; - } -}; - -$.fn.position = function( options ) { - if ( !options || !options.of ) { - return _position.apply( this, arguments ); - } - - // Make a copy, we don't want to modify arguments - options = $.extend( {}, options ); - - var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, - target = $( options.of ), - within = $.position.getWithinInfo( options.within ), - scrollInfo = $.position.getScrollInfo( within ), - collision = ( options.collision || "flip" ).split( " " ), - offsets = {}; - - dimensions = getDimensions( target ); - if ( target[ 0 ].preventDefault ) { - - // Force left top to allow flipping - options.at = "left top"; - } - targetWidth = dimensions.width; - targetHeight = dimensions.height; - targetOffset = dimensions.offset; - - // Clone to reuse original targetOffset later - basePosition = $.extend( {}, targetOffset ); - - // Force my and at to have valid horizontal and vertical positions - // if a value is missing or invalid, it will be converted to center - $.each( [ "my", "at" ], function() { - var pos = ( options[ this ] || "" ).split( " " ), - horizontalOffset, - verticalOffset; - - if ( pos.length === 1 ) { - pos = rhorizontal.test( pos[ 0 ] ) ? - pos.concat( [ "center" ] ) : - rvertical.test( pos[ 0 ] ) ? - [ "center" ].concat( pos ) : - [ "center", "center" ]; - } - pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; - pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; - - // Calculate offsets - horizontalOffset = roffset.exec( pos[ 0 ] ); - verticalOffset = roffset.exec( pos[ 1 ] ); - offsets[ this ] = [ - horizontalOffset ? horizontalOffset[ 0 ] : 0, - verticalOffset ? verticalOffset[ 0 ] : 0 - ]; - - // Reduce to just the positions without the offsets - options[ this ] = [ - rposition.exec( pos[ 0 ] )[ 0 ], - rposition.exec( pos[ 1 ] )[ 0 ] - ]; - } ); - - // Normalize collision option - if ( collision.length === 1 ) { - collision[ 1 ] = collision[ 0 ]; - } - - if ( options.at[ 0 ] === "right" ) { - basePosition.left += targetWidth; - } else if ( options.at[ 0 ] === "center" ) { - basePosition.left += targetWidth / 2; - } - - if ( options.at[ 1 ] === "bottom" ) { - basePosition.top += targetHeight; - } else if ( options.at[ 1 ] === "center" ) { - basePosition.top += targetHeight / 2; - } - - atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); - basePosition.left += atOffset[ 0 ]; - basePosition.top += atOffset[ 1 ]; - - return this.each( function() { - var collisionPosition, using, - elem = $( this ), - elemWidth = elem.outerWidth(), - elemHeight = elem.outerHeight(), - marginLeft = parseCss( this, "marginLeft" ), - marginTop = parseCss( this, "marginTop" ), - collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + - scrollInfo.width, - collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + - scrollInfo.height, - position = $.extend( {}, basePosition ), - myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); - - if ( options.my[ 0 ] === "right" ) { - position.left -= elemWidth; - } else if ( options.my[ 0 ] === "center" ) { - position.left -= elemWidth / 2; - } - - if ( options.my[ 1 ] === "bottom" ) { - position.top -= elemHeight; - } else if ( options.my[ 1 ] === "center" ) { - position.top -= elemHeight / 2; - } - - position.left += myOffset[ 0 ]; - position.top += myOffset[ 1 ]; - - collisionPosition = { - marginLeft: marginLeft, - marginTop: marginTop - }; - - $.each( [ "left", "top" ], function( i, dir ) { - if ( $.ui.position[ collision[ i ] ] ) { - $.ui.position[ collision[ i ] ][ dir ]( position, { - targetWidth: targetWidth, - targetHeight: targetHeight, - elemWidth: elemWidth, - elemHeight: elemHeight, - collisionPosition: collisionPosition, - collisionWidth: collisionWidth, - collisionHeight: collisionHeight, - offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], - my: options.my, - at: options.at, - within: within, - elem: elem - } ); - } - } ); - - if ( options.using ) { - - // Adds feedback as second argument to using callback, if present - using = function( props ) { - var left = targetOffset.left - position.left, - right = left + targetWidth - elemWidth, - top = targetOffset.top - position.top, - bottom = top + targetHeight - elemHeight, - feedback = { - target: { - element: target, - left: targetOffset.left, - top: targetOffset.top, - width: targetWidth, - height: targetHeight - }, - element: { - element: elem, - left: position.left, - top: position.top, - width: elemWidth, - height: elemHeight - }, - horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", - vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" - }; - if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { - feedback.horizontal = "center"; - } - if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { - feedback.vertical = "middle"; - } - if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { - feedback.important = "horizontal"; - } else { - feedback.important = "vertical"; - } - options.using.call( this, props, feedback ); - }; - } - - elem.offset( $.extend( position, { using: using } ) ); - } ); -}; - -$.ui.position = { - fit: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, - outerWidth = within.width, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = withinOffset - collisionPosLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, - newOverRight; - - // Element is wider than within - if ( data.collisionWidth > outerWidth ) { - - // Element is initially over the left side of within - if ( overLeft > 0 && overRight <= 0 ) { - newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - - withinOffset; - position.left += overLeft - newOverRight; - - // Element is initially over right side of within - } else if ( overRight > 0 && overLeft <= 0 ) { - position.left = withinOffset; - - // Element is initially over both left and right sides of within - } else { - if ( overLeft > overRight ) { - position.left = withinOffset + outerWidth - data.collisionWidth; - } else { - position.left = withinOffset; - } - } - - // Too far left -> align with left edge - } else if ( overLeft > 0 ) { - position.left += overLeft; - - // Too far right -> align with right edge - } else if ( overRight > 0 ) { - position.left -= overRight; - - // Adjust based on position and margin - } else { - position.left = max( position.left - collisionPosLeft, position.left ); - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollTop : within.offset.top, - outerHeight = data.within.height, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = withinOffset - collisionPosTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, - newOverBottom; - - // Element is taller than within - if ( data.collisionHeight > outerHeight ) { - - // Element is initially over the top of within - if ( overTop > 0 && overBottom <= 0 ) { - newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - - withinOffset; - position.top += overTop - newOverBottom; - - // Element is initially over bottom of within - } else if ( overBottom > 0 && overTop <= 0 ) { - position.top = withinOffset; - - // Element is initially over both top and bottom of within - } else { - if ( overTop > overBottom ) { - position.top = withinOffset + outerHeight - data.collisionHeight; - } else { - position.top = withinOffset; - } - } - - // Too far up -> align with top - } else if ( overTop > 0 ) { - position.top += overTop; - - // Too far down -> align with bottom edge - } else if ( overBottom > 0 ) { - position.top -= overBottom; - - // Adjust based on position and margin - } else { - position.top = max( position.top - collisionPosTop, position.top ); - } - } - }, - flip: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.offset.left + within.scrollLeft, - outerWidth = within.width, - offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = collisionPosLeft - offsetLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, - myOffset = data.my[ 0 ] === "left" ? - -data.elemWidth : - data.my[ 0 ] === "right" ? - data.elemWidth : - 0, - atOffset = data.at[ 0 ] === "left" ? - data.targetWidth : - data.at[ 0 ] === "right" ? - -data.targetWidth : - 0, - offset = -2 * data.offset[ 0 ], - newOverRight, - newOverLeft; - - if ( overLeft < 0 ) { - newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - - outerWidth - withinOffset; - if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { - position.left += myOffset + atOffset + offset; - } - } else if ( overRight > 0 ) { - newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + - atOffset + offset - offsetLeft; - if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { - position.left += myOffset + atOffset + offset; - } - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.offset.top + within.scrollTop, - outerHeight = within.height, - offsetTop = within.isWindow ? within.scrollTop : within.offset.top, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = collisionPosTop - offsetTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, - top = data.my[ 1 ] === "top", - myOffset = top ? - -data.elemHeight : - data.my[ 1 ] === "bottom" ? - data.elemHeight : - 0, - atOffset = data.at[ 1 ] === "top" ? - data.targetHeight : - data.at[ 1 ] === "bottom" ? - -data.targetHeight : - 0, - offset = -2 * data.offset[ 1 ], - newOverTop, - newOverBottom; - if ( overTop < 0 ) { - newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - - outerHeight - withinOffset; - if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { - position.top += myOffset + atOffset + offset; - } - } else if ( overBottom > 0 ) { - newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + - offset - offsetTop; - if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { - position.top += myOffset + atOffset + offset; - } - } - } - }, - flipfit: { - left: function() { - $.ui.position.flip.left.apply( this, arguments ); - $.ui.position.fit.left.apply( this, arguments ); - }, - top: function() { - $.ui.position.flip.top.apply( this, arguments ); - $.ui.position.fit.top.apply( this, arguments ); - } - } -}; - -} )(); - -var position = $.ui.position; - - -/*! - * jQuery UI :data 1.12.1 + ( function() { + var cachedScrollbarWidth, + max = Math.max, + abs = Math.abs, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + + function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; + } + + function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; + } + + function isWindow( obj ) { + return obj != null && obj === obj.window; + } + + function getDimensions( elem ) { + var raw = elem[ 0 ]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; + } + + $.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "<div style=" + + "'display:block;position:absolute;width:200px;height:200px;overflow:hidden;'>" + + "<div style='height:300px;width:auto;'></div></div>" ), + innerDiv = div.children()[ 0 ]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[ 0 ].clientWidth; + } + + div.remove(); + + return ( cachedScrollbarWidth = w1 - w2 ); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-x" ), + overflowY = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isElemWindow = isWindow( withinElement[ 0 ] ), + isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, + hasOffset = !isElemWindow && !isDocument; + return { + element: withinElement, + isWindow: isElemWindow, + isDocument: isDocument, + offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: withinElement.outerWidth(), + height: withinElement.outerHeight() + }; + } + }; + + $.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // Make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + + // Make sure string options are treated as CSS selectors + target = typeof options.of === "string" ? + $( document ).find( options.of ) : + $( options.of ), + + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[ 0 ].preventDefault ) { + + // Force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + + // Clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // Force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1 ) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // Calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // Reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + } ); + + // Normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each( function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem: elem + } ); + } + } ); + + if ( options.using ) { + + // Adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + } ); + }; + + $.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // Element is wider than within + if ( data.collisionWidth > outerWidth ) { + + // Element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - + withinOffset; + position.left += overLeft - newOverRight; + + // Element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + + // Element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + + // Too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + + // Too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + + // Adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // Element is taller than within + if ( data.collisionHeight > outerHeight ) { + + // Element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - + withinOffset; + position.top += overTop - newOverBottom; + + // Element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + + // Element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + + // Too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + + // Too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + + // Adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - + outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - + outerHeight - withinOffset; + if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { + position.top += myOffset + atOffset + offset; + } + } else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + + offset - offsetTop; + if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } + }; + + } )(); + + var position = $.ui.position; + + + /*! + * jQuery UI :data 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1246,22 +1273,22 @@ var position = $.ui.position; //>>docs: http://api.jqueryui.com/data-selector/ -var data = $.extend( $.expr[ ":" ], { - data: $.expr.createPseudo ? - $.expr.createPseudo( function( dataName ) { - return function( elem ) { - return !!$.data( elem, dataName ); - }; - } ) : + var data = $.extend( $.expr.pseudos, { + data: $.expr.createPseudo ? + $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) : - // Support: jQuery <1.8 - function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - } -} ); + // Support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + } + } ); -/*! - * jQuery UI Disable Selection 1.12.1 + /*! + * jQuery UI Disable Selection 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1276,28 +1303,27 @@ var data = $.extend( $.expr[ ":" ], { // This file is deprecated + var disableSelection = $.fn.extend( { + disableSelection: ( function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; -var disableSelection = $.fn.extend( { - disableSelection: ( function() { - var eventType = "onselectstart" in document.createElement( "div" ) ? - "selectstart" : - "mousedown"; + return function() { + return this.on( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + } ); + }; + } )(), - return function() { - return this.on( eventType + ".ui-disableSelection", function( event ) { - event.preventDefault(); - } ); - }; - } )(), + enableSelection: function() { + return this.off( ".ui-disableSelection" ); + } + } ); - enableSelection: function() { - return this.off( ".ui-disableSelection" ); - } -} ); - -/*! - * jQuery UI Focusable 1.12.1 + /*! + * jQuery UI Focusable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1311,77 +1337,75 @@ var disableSelection = $.fn.extend( { //>>docs: http://api.jqueryui.com/focusable-selector/ - // Selectors -$.ui.focusable = function( element, hasTabindex ) { - var map, mapName, img, focusableIfVisible, fieldset, - nodeName = element.nodeName.toLowerCase(); - - if ( "area" === nodeName ) { - map = element.parentNode; - mapName = map.name; - if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { - return false; - } - img = $( "img[usemap='#" + mapName + "']" ); - return img.length > 0 && img.is( ":visible" ); - } - - if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) { - focusableIfVisible = !element.disabled; - - if ( focusableIfVisible ) { - - // Form controls within a disabled fieldset are disabled. - // However, controls within the fieldset's legend do not get disabled. - // Since controls generally aren't placed inside legends, we skip - // this portion of the check. - fieldset = $( element ).closest( "fieldset" )[ 0 ]; - if ( fieldset ) { - focusableIfVisible = !fieldset.disabled; - } - } - } else if ( "a" === nodeName ) { - focusableIfVisible = element.href || hasTabindex; - } else { - focusableIfVisible = hasTabindex; - } - - return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); -}; + $.ui.focusable = function( element, hasTabindex ) { + var map, mapName, img, focusableIfVisible, fieldset, + nodeName = element.nodeName.toLowerCase(); + + if ( "area" === nodeName ) { + map = element.parentNode; + mapName = map.name; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap='#" + mapName + "']" ); + return img.length > 0 && img.is( ":visible" ); + } + + if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) { + focusableIfVisible = !element.disabled; + + if ( focusableIfVisible ) { + + // Form controls within a disabled fieldset are disabled. + // However, controls within the fieldset's legend do not get disabled. + // Since controls generally aren't placed inside legends, we skip + // this portion of the check. + fieldset = $( element ).closest( "fieldset" )[ 0 ]; + if ( fieldset ) { + focusableIfVisible = !fieldset.disabled; + } + } + } else if ( "a" === nodeName ) { + focusableIfVisible = element.href || hasTabindex; + } else { + focusableIfVisible = hasTabindex; + } + + return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); + }; // Support: IE 8 only // IE 8 doesn't resolve inherit to visible/hidden for computed values -function visible( element ) { - var visibility = element.css( "visibility" ); - while ( visibility === "inherit" ) { - element = element.parent(); - visibility = element.css( "visibility" ); - } - return visibility !== "hidden"; -} - -$.extend( $.expr[ ":" ], { - focusable: function( element ) { - return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); - } -} ); + function visible( element ) { + var visibility = element.css( "visibility" ); + while ( visibility === "inherit" ) { + element = element.parent(); + visibility = element.css( "visibility" ); + } + return visibility === "visible"; + } -var focusable = $.ui.focusable; + $.extend( $.expr.pseudos, { + focusable: function( element ) { + return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); + } + } ); + var focusable = $.ui.focusable; // Support: IE8 Only // IE8 does not support the form attribute and when it is supplied. It overwrites the form prop // with a string, so we need to find the proper form. -var form = $.fn.form = function() { - return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); -}; + var form = $.fn._form = function() { + return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); + }; -/*! - * jQuery UI Form Reset Mixin 1.12.1 + /*! + * jQuery UI Form Reset Mixin 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1395,56 +1419,55 @@ var form = $.fn.form = function() { //>>docs: http://api.jqueryui.com/form-reset-mixin/ - -var formResetMixin = $.ui.formResetMixin = { - _formResetHandler: function() { - var form = $( this ); - - // Wait for the form reset to actually happen before refreshing - setTimeout( function() { - var instances = form.data( "ui-form-reset-instances" ); - $.each( instances, function() { - this.refresh(); - } ); - } ); - }, - - _bindFormResetHandler: function() { - this.form = this.element.form(); - if ( !this.form.length ) { - return; - } - - var instances = this.form.data( "ui-form-reset-instances" ) || []; - if ( !instances.length ) { - - // We don't use _on() here because we use a single event handler per form - this.form.on( "reset.ui-form-reset", this._formResetHandler ); - } - instances.push( this ); - this.form.data( "ui-form-reset-instances", instances ); - }, - - _unbindFormResetHandler: function() { - if ( !this.form.length ) { - return; - } - - var instances = this.form.data( "ui-form-reset-instances" ); - instances.splice( $.inArray( this, instances ), 1 ); - if ( instances.length ) { - this.form.data( "ui-form-reset-instances", instances ); - } else { - this.form - .removeData( "ui-form-reset-instances" ) - .off( "reset.ui-form-reset" ); - } - } -}; - - -/*! - * jQuery UI Support for jQuery core 1.7.x 1.12.1 + var formResetMixin = $.ui.formResetMixin = { + _formResetHandler: function() { + var form = $( this ); + + // Wait for the form reset to actually happen before refreshing + setTimeout( function() { + var instances = form.data( "ui-form-reset-instances" ); + $.each( instances, function() { + this.refresh(); + } ); + } ); + }, + + _bindFormResetHandler: function() { + this.form = this.element._form(); + if ( !this.form.length ) { + return; + } + + var instances = this.form.data( "ui-form-reset-instances" ) || []; + if ( !instances.length ) { + + // We don't use _on() here because we use a single event handler per form + this.form.on( "reset.ui-form-reset", this._formResetHandler ); + } + instances.push( this ); + this.form.data( "ui-form-reset-instances", instances ); + }, + + _unbindFormResetHandler: function() { + if ( !this.form.length ) { + return; + } + + var instances = this.form.data( "ui-form-reset-instances" ); + instances.splice( $.inArray( this, instances ), 1 ); + if ( instances.length ) { + this.form.data( "ui-form-reset-instances", instances ); + } else { + this.form + .removeData( "ui-form-reset-instances" ) + .off( "reset.ui-form-reset" ); + } + } + }; + + + /*! + * jQuery UI Support for jQuery core 1.8.x and newer 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1453,77 +1476,73 @@ var formResetMixin = $.ui.formResetMixin = { * */ -//>>label: jQuery 1.7 Support +//>>label: jQuery 1.8+ Support //>>group: Core -//>>description: Support version 1.7.x of jQuery core - - - -// Support: jQuery 1.7 only -// Not a great way to check versions, but since we only support 1.7+ and only -// need to detect <1.8, this is a simple check that should suffice. Checking -// for "1.7." would be a bit safer, but the version string is 1.7, not 1.7.0 -// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting -// 1.7 anymore). See #11197 for why we're not using feature detection. -if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) { - - // Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight() - // Unlike jQuery Core 1.8+, these only support numeric values to set the - // dimensions in pixels - $.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; - - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; - if ( border ) { - size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; - } - } ); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - - return this.each( function() { - $( this ).css( type, reduce( this, size ) + "px" ); - } ); - }; - - $.fn[ "outer" + name ] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } - - return this.each( function() { - $( this ).css( type, reduce( this, size, true, margin ) + "px" ); - } ); - }; - } ); - - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - -; -/*! - * jQuery UI Keycode 1.12.1 +//>>description: Support version 1.8.x and newer of jQuery core + + +// Support: jQuery 1.9.x or older +// $.expr[ ":" ] is deprecated. + if ( !$.expr.pseudos ) { + $.expr.pseudos = $.expr[ ":" ]; + } + +// Support: jQuery 1.11.x or older +// $.unique has been renamed to $.uniqueSort + if ( !$.uniqueSort ) { + $.uniqueSort = $.unique; + } + +// Support: jQuery 2.2.x or older. +// This method has been defined in jQuery 3.0.0. +// Code from https://github.com/jquery/jquery/blob/e539bac79e666bba95bba86d690b4e609dca2286/src/selector/escapeSelector.js + if ( !$.escapeSelector ) { + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + + var fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }; + + $.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); + }; + } + +// Support: jQuery 3.4.x or older +// These methods have been defined in jQuery 3.5.0. + if ( !$.fn.even || !$.fn.odd ) { + $.fn.extend( { + even: function() { + return this.filter( function( i ) { + return i % 2 === 0; + } ); + }, + odd: function() { + return this.filter( function( i ) { + return i % 2 === 1; + } ); + } + } ); + } + + ; + /*! + * jQuery UI Keycode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1537,39 +1556,28 @@ if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) { //>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ -var keycode = $.ui.keyCode = { - BACKSPACE: 8, - COMMA: 188, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - LEFT: 37, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SPACE: 32, - TAB: 9, - UP: 38 -}; - - - - -// Internal use only -var escapeSelector = $.ui.escapeSelector = ( function() { - var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g; - return function( selector ) { - return selector.replace( selectorEscape, "\\$1" ); - }; -} )(); - - -/*! - * jQuery UI Labels 1.12.1 + var keycode = $.ui.keyCode = { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 + }; + + + /*! + * jQuery UI Labels 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1583,45 +1591,48 @@ var escapeSelector = $.ui.escapeSelector = ( function() { //>>docs: http://api.jqueryui.com/labels/ + var labels = $.fn.labels = function() { + var ancestor, selector, id, labels, ancestors; -var labels = $.fn.labels = function() { - var ancestor, selector, id, labels, ancestors; + if ( !this.length ) { + return this.pushStack( [] ); + } - // Check control.labels first - if ( this[ 0 ].labels && this[ 0 ].labels.length ) { - return this.pushStack( this[ 0 ].labels ); - } + // Check control.labels first + if ( this[ 0 ].labels && this[ 0 ].labels.length ) { + return this.pushStack( this[ 0 ].labels ); + } - // Support: IE <= 11, FF <= 37, Android <= 2.3 only - // Above browsers do not support control.labels. Everything below is to support them - // as well as document fragments. control.labels does not work on document fragments - labels = this.eq( 0 ).parents( "label" ); + // Support: IE <= 11, FF <= 37, Android <= 2.3 only + // Above browsers do not support control.labels. Everything below is to support them + // as well as document fragments. control.labels does not work on document fragments + labels = this.eq( 0 ).parents( "label" ); - // Look for the label based on the id - id = this.attr( "id" ); - if ( id ) { + // Look for the label based on the id + id = this.attr( "id" ); + if ( id ) { - // We don't search against the document in case the element - // is disconnected from the DOM - ancestor = this.eq( 0 ).parents().last(); + // We don't search against the document in case the element + // is disconnected from the DOM + ancestor = this.eq( 0 ).parents().last(); - // Get a full set of top level ancestors - ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); + // Get a full set of top level ancestors + ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); - // Create a selector for the label based on the id - selector = "label[for='" + $.ui.escapeSelector( id ) + "']"; + // Create a selector for the label based on the id + selector = "label[for='" + $.escapeSelector( id ) + "']"; - labels = labels.add( ancestors.find( selector ).addBack( selector ) ); + labels = labels.add( ancestors.find( selector ).addBack( selector ) ); - } + } - // Return whatever we have found for labels - return this.pushStack( labels ); -}; + // Return whatever we have found for labels + return this.pushStack( labels ); + }; -/*! - * jQuery UI Scroll Parent 1.12.1 + /*! + * jQuery UI Scroll Parent 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1635,28 +1646,27 @@ var labels = $.fn.labels = function() { //>>docs: http://api.jqueryui.com/scrollParent/ + var scrollParent = $.fn.scrollParent = function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + + parent.css( "overflow-x" ) ); + } ).eq( 0 ); -var scrollParent = $.fn.scrollParent = function( includeHidden ) { - var position = this.css( "position" ), - excludeStaticParent = position === "absolute", - overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, - scrollParent = this.parents().filter( function() { - var parent = $( this ); - if ( excludeStaticParent && parent.css( "position" ) === "static" ) { - return false; - } - return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + - parent.css( "overflow-x" ) ); - } ).eq( 0 ); - - return position === "fixed" || !scrollParent.length ? - $( this[ 0 ].ownerDocument || document ) : - scrollParent; -}; + return position === "fixed" || !scrollParent.length ? + $( this[ 0 ].ownerDocument || document ) : + scrollParent; + }; -/*! - * jQuery UI Tabbable 1.12.1 + /*! + * jQuery UI Tabbable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1670,18 +1680,17 @@ var scrollParent = $.fn.scrollParent = function( includeHidden ) { //>>docs: http://api.jqueryui.com/tabbable-selector/ - -var tabbable = $.extend( $.expr[ ":" ], { - tabbable: function( element ) { - var tabIndex = $.attr( element, "tabindex" ), - hasTabindex = tabIndex != null; - return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex ); - } -} ); + var tabbable = $.extend( $.expr.pseudos, { + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ), + hasTabindex = tabIndex != null; + return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex ); + } + } ); -/*! - * jQuery UI Unique ID 1.12.1 + /*! + * jQuery UI Unique ID 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1695,37 +1704,35 @@ var tabbable = $.extend( $.expr[ ":" ], { //>>docs: http://api.jqueryui.com/uniqueId/ + var uniqueId = $.fn.extend( { + uniqueId: ( function() { + var uuid = 0; -var uniqueId = $.fn.extend( { - uniqueId: ( function() { - var uuid = 0; - - return function() { - return this.each( function() { - if ( !this.id ) { - this.id = "ui-id-" + ( ++uuid ); - } - } ); - }; - } )(), - - removeUniqueId: function() { - return this.each( function() { - if ( /^ui-id-\d+$/.test( this.id ) ) { - $( this ).removeAttr( "id" ); - } - } ); - } -} ); + return function() { + return this.each( function() { + if ( !this.id ) { + this.id = "ui-id-" + ( ++uuid ); + } + } ); + }; + } )(), + removeUniqueId: function() { + return this.each( function() { + if ( /^ui-id-\d+$/.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + } ); + } + } ); // This file is deprecated -var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); -/*! - * jQuery UI Mouse 1.12.1 + /*! + * jQuery UI Mouse 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -1739,279 +1746,285 @@ var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); //>>docs: http://api.jqueryui.com/mouse/ - -var mouseHandled = false; -$( document ).on( "mouseup", function() { - mouseHandled = false; -} ); - -var widgetsMouse = $.widget( "ui.mouse", { - version: "1.12.1", - options: { - cancel: "input, textarea, button, select, option", - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var that = this; - - this.element - .on( "mousedown." + this.widgetName, function( event ) { - return that._mouseDown( event ); - } ) - .on( "click." + this.widgetName, function( event ) { - if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, that.widgetName + ".preventClickEvent" ); - event.stopImmediatePropagation(); - return false; - } - } ); - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.off( "." + this.widgetName ); - if ( this._mouseMoveDelegate ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - } - }, - - _mouseDown: function( event ) { - - // don't let more than one widget handle mouseStart - if ( mouseHandled ) { - return; - } - - this._mouseMoved = false; - - // We may have missed mouseup (out of window) - ( this._mouseStarted && this._mouseUp( event ) ); - - this._mouseDownEvent = event; - - var that = this, - btnIsLeft = ( event.which === 1 ), - - // event.target.nodeName works around a bug in IE 8 with - // disabled inputs (#7620) - elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? - $( event.target ).closest( this.options.cancel ).length : false ); - if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if ( !this.mouseDelayMet ) { - this._mouseDelayTimer = setTimeout( function() { - that.mouseDelayMet = true; - }, this.options.delay ); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = ( this._mouseStart( event ) !== false ); - if ( !this._mouseStarted ) { - event.preventDefault(); - return true; - } - } - - // Click event may never have fired (Gecko & Opera) - if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, this.widgetName + ".preventClickEvent" ); - } - - // These delegates are required to keep context - this._mouseMoveDelegate = function( event ) { - return that._mouseMove( event ); - }; - this._mouseUpDelegate = function( event ) { - return that._mouseUp( event ); - }; - - this.document - .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function( event ) { - - // Only check for mouseups outside the document if you've moved inside the document - // at least once. This prevents the firing of mouseup in the case of IE<9, which will - // fire a mousemove event if content is placed under the cursor. See #7778 - // Support: IE <9 - if ( this._mouseMoved ) { - - // IE mouseup check - mouseup happened when mouse was out of window - if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && - !event.button ) { - return this._mouseUp( event ); - - // Iframe mouseup check - mouseup occurred in another document - } else if ( !event.which ) { - - // Support: Safari <=8 - 9 - // Safari sets which to 0 if you press any of the following keys - // during a drag (#14461) - if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || - event.originalEvent.metaKey || event.originalEvent.shiftKey ) { - this.ignoreMissingWhich = true; - } else if ( !this.ignoreMissingWhich ) { - return this._mouseUp( event ); - } - } - } - - if ( event.which || event.button ) { - this._mouseMoved = true; - } - - if ( this._mouseStarted ) { - this._mouseDrag( event ); - return event.preventDefault(); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = - ( this._mouseStart( this._mouseDownEvent, event ) !== false ); - ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); - } - - return !this._mouseStarted; - }, - - _mouseUp: function( event ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - if ( this._mouseStarted ) { - this._mouseStarted = false; - - if ( event.target === this._mouseDownEvent.target ) { - $.data( event.target, this.widgetName + ".preventClickEvent", true ); - } - - this._mouseStop( event ); - } - - if ( this._mouseDelayTimer ) { - clearTimeout( this._mouseDelayTimer ); - delete this._mouseDelayTimer; - } - - this.ignoreMissingWhich = false; - mouseHandled = false; - event.preventDefault(); - }, - - _mouseDistanceMet: function( event ) { - return ( Math.max( - Math.abs( this._mouseDownEvent.pageX - event.pageX ), - Math.abs( this._mouseDownEvent.pageY - event.pageY ) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function( /* event */ ) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function( /* event */ ) {}, - _mouseDrag: function( /* event */ ) {}, - _mouseStop: function( /* event */ ) {}, - _mouseCapture: function( /* event */ ) { return true; } -} ); - + var mouseHandled = false; + $( document ).on( "mouseup", function() { + mouseHandled = false; + } ); + + var widgetsMouse = $.widget( "ui.mouse", { + version: "1.13.0", + options: { + cancel: "input, textarea, button, select, option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .on( "mousedown." + this.widgetName, function( event ) { + return that._mouseDown( event ); + } ) + .on( "click." + this.widgetName, function( event ) { + if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, that.widgetName + ".preventClickEvent" ); + event.stopImmediatePropagation(); + return false; + } + } ); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.off( "." + this.widgetName ); + if ( this._mouseMoveDelegate ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + } + }, + + _mouseDown: function( event ) { + + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // We may have missed mouseup (out of window) + if ( this._mouseStarted ) { + this._mouseUp( event ); + } + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = ( event.which === 1 ), + + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? + $( event.target ).closest( this.options.cancel ).length : false ); + if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if ( !this.mouseDelayMet ) { + this._mouseDelayTimer = setTimeout( function() { + that.mouseDelayMet = true; + }, this.options.delay ); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = ( this._mouseStart( event ) !== false ); + if ( !this._mouseStarted ) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, this.widgetName + ".preventClickEvent" ); + } + + // These delegates are required to keep context + this._mouseMoveDelegate = function( event ) { + return that._mouseMove( event ); + }; + this._mouseUpDelegate = function( event ) { + return that._mouseUp( event ); + }; + + this.document + .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function( event ) { + + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + + // IE mouseup check - mouseup happened when mouse was out of window + if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && + !event.button ) { + return this._mouseUp( event ); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + + // Support: Safari <=8 - 9 + // Safari sets which to 0 if you press any of the following keys + // during a drag (#14461) + if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || + event.originalEvent.metaKey || event.originalEvent.shiftKey ) { + this.ignoreMissingWhich = true; + } else if ( !this.ignoreMissingWhich ) { + return this._mouseUp( event ); + } + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if ( this._mouseStarted ) { + this._mouseDrag( event ); + return event.preventDefault(); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = + ( this._mouseStart( this._mouseDownEvent, event ) !== false ); + if ( this._mouseStarted ) { + this._mouseDrag( event ); + } else { + this._mouseUp( event ); + } + } + + return !this._mouseStarted; + }, + + _mouseUp: function( event ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if ( this._mouseStarted ) { + this._mouseStarted = false; + + if ( event.target === this._mouseDownEvent.target ) { + $.data( event.target, this.widgetName + ".preventClickEvent", true ); + } + + this._mouseStop( event ); + } + + if ( this._mouseDelayTimer ) { + clearTimeout( this._mouseDelayTimer ); + delete this._mouseDelayTimer; + } + + this.ignoreMissingWhich = false; + mouseHandled = false; + event.preventDefault(); + }, + + _mouseDistanceMet: function( event ) { + return ( Math.max( + Math.abs( this._mouseDownEvent.pageX - event.pageX ), + Math.abs( this._mouseDownEvent.pageY - event.pageY ) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function( /* event */ ) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function( /* event */ ) {}, + _mouseDrag: function( /* event */ ) {}, + _mouseStop: function( /* event */ ) {}, + _mouseCapture: function( /* event */ ) { + return true; + } + } ); // $.ui.plugin is deprecated. Use $.widget() extensions instead. -var plugin = $.ui.plugin = { - add: function( module, option, set ) { - var i, - proto = $.ui[ module ].prototype; - for ( i in set ) { - proto.plugins[ i ] = proto.plugins[ i ] || []; - proto.plugins[ i ].push( [ option, set[ i ] ] ); - } - }, - call: function( instance, name, args, allowDisconnected ) { - var i, - set = instance.plugins[ name ]; - - if ( !set ) { - return; - } - - if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || - instance.element[ 0 ].parentNode.nodeType === 11 ) ) { - return; - } - - for ( i = 0; i < set.length; i++ ) { - if ( instance.options[ set[ i ][ 0 ] ] ) { - set[ i ][ 1 ].apply( instance.element, args ); - } - } - } -}; - - - -var safeActiveElement = $.ui.safeActiveElement = function( document ) { - var activeElement; - - // Support: IE 9 only - // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe> - try { - activeElement = document.activeElement; - } catch ( error ) { - activeElement = document.body; - } - - // Support: IE 9 - 11 only - // IE may return null instead of an element - // Interestingly, this only seems to occur when NOT in an iframe - if ( !activeElement ) { - activeElement = document.body; - } - - // Support: IE 11 only - // IE11 returns a seemingly empty object in some cases when accessing - // document.activeElement from an <iframe> - if ( !activeElement.nodeName ) { - activeElement = document.body; - } - - return activeElement; -}; - - - -var safeBlur = $.ui.safeBlur = function( element ) { - - // Support: IE9 - 10 only - // If the <body> is blurred, IE will switch windows, see #9420 - if ( element && element.nodeName.toLowerCase() !== "body" ) { - $( element ).trigger( "blur" ); - } -}; - - -/*! - * jQuery UI Draggable 1.12.1 + var plugin = $.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || + instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } + }; + + + + var safeActiveElement = $.ui.safeActiveElement = function( document ) { + var activeElement; + + // Support: IE 9 only + // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe> + try { + activeElement = document.activeElement; + } catch ( error ) { + activeElement = document.body; + } + + // Support: IE 9 - 11 only + // IE may return null instead of an element + // Interestingly, this only seems to occur when NOT in an iframe + if ( !activeElement ) { + activeElement = document.body; + } + + // Support: IE 11 only + // IE11 returns a seemingly empty object in some cases when accessing + // document.activeElement from an <iframe> + if ( !activeElement.nodeName ) { + activeElement = document.body; + } + + return activeElement; + }; + + + + var safeBlur = $.ui.safeBlur = function( element ) { + + // Support: IE9 - 10 only + // If the <body> is blurred, IE will switch windows, see #9420 + if ( element && element.nodeName.toLowerCase() !== "body" ) { + $( element ).trigger( "blur" ); + } + }; + + + /*! + * jQuery UI Draggable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -2027,1221 +2040,1226 @@ var safeBlur = $.ui.safeBlur = function( element ) { //>>css.structure: ../../themes/base/draggable.css - -$.widget( "ui.draggable", $.ui.mouse, { - version: "1.12.1", - widgetEventPrefix: "drag", - options: { - addClasses: true, - appendTo: "parent", - axis: false, - connectToSortable: false, - containment: false, - cursor: "auto", - cursorAt: false, - grid: false, - handle: false, - helper: "original", - iframeFix: false, - opacity: false, - refreshPositions: false, - revert: false, - revertDuration: 500, - scope: "default", - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - snap: false, - snapMode: "both", - snapTolerance: 20, - stack: false, - zIndex: false, - - // Callbacks - drag: null, - start: null, - stop: null - }, - _create: function() { - - if ( this.options.helper === "original" ) { - this._setPositionRelative(); - } - if ( this.options.addClasses ) { - this._addClass( "ui-draggable" ); - } - this._setHandleClassName(); - - this._mouseInit(); - }, - - _setOption: function( key, value ) { - this._super( key, value ); - if ( key === "handle" ) { - this._removeHandleClassName(); - this._setHandleClassName(); - } - }, - - _destroy: function() { - if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { - this.destroyOnClear = true; - return; - } - this._removeHandleClassName(); - this._mouseDestroy(); - }, - - _mouseCapture: function( event ) { - var o = this.options; - - // Among others, prevent a drag on a resizable-handle - if ( this.helper || o.disabled || - $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) { - return false; - } - - //Quit if we're not on a valid handle - this.handle = this._getHandle( event ); - if ( !this.handle ) { - return false; - } - - this._blurActiveElement( event ); - - this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); - - return true; - - }, - - _blockFrames: function( selector ) { - this.iframeBlocks = this.document.find( selector ).map( function() { - var iframe = $( this ); - - return $( "<div>" ) - .css( "position", "absolute" ) - .appendTo( iframe.parent() ) - .outerWidth( iframe.outerWidth() ) - .outerHeight( iframe.outerHeight() ) - .offset( iframe.offset() )[ 0 ]; - } ); - }, - - _unblockFrames: function() { - if ( this.iframeBlocks ) { - this.iframeBlocks.remove(); - delete this.iframeBlocks; - } - }, - - _blurActiveElement: function( event ) { - var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), - target = $( event.target ); - - // Don't blur if the event occurred on an element that is within - // the currently focused element - // See #10527, #12472 - if ( target.closest( activeElement ).length ) { - return; - } - - // Blur any element that currently has focus, see #4261 - $.ui.safeBlur( activeElement ); - }, - - _mouseStart: function( event ) { - - var o = this.options; - - //Create and append the visible helper - this.helper = this._createHelper( event ); - - this._addClass( this.helper, "ui-draggable-dragging" ); - - //Cache the helper size - this._cacheHelperProportions(); - - //If ddmanager is used for droppables, set the global draggable - if ( $.ui.ddmanager ) { - $.ui.ddmanager.current = this; - } - - /* + $.widget( "ui.draggable", $.ui.mouse, { + version: "1.13.0", + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false, + + // Callbacks + drag: null, + start: null, + stop: null + }, + _create: function() { + + if ( this.options.helper === "original" ) { + this._setPositionRelative(); + } + if ( this.options.addClasses ) { + this._addClass( "ui-draggable" ); + } + this._setHandleClassName(); + + this._mouseInit(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "handle" ) { + this._removeHandleClassName(); + this._setHandleClassName(); + } + }, + + _destroy: function() { + if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { + this.destroyOnClear = true; + return; + } + this._removeHandleClassName(); + this._mouseDestroy(); + }, + + _mouseCapture: function( event ) { + var o = this.options; + + // Among others, prevent a drag on a resizable-handle + if ( this.helper || o.disabled || + $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) { + return false; + } + + //Quit if we're not on a valid handle + this.handle = this._getHandle( event ); + if ( !this.handle ) { + return false; + } + + this._blurActiveElement( event ); + + this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); + + return true; + + }, + + _blockFrames: function( selector ) { + this.iframeBlocks = this.document.find( selector ).map( function() { + var iframe = $( this ); + + return $( "<div>" ) + .css( "position", "absolute" ) + .appendTo( iframe.parent() ) + .outerWidth( iframe.outerWidth() ) + .outerHeight( iframe.outerHeight() ) + .offset( iframe.offset() )[ 0 ]; + } ); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _blurActiveElement: function( event ) { + var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), + target = $( event.target ); + + // Don't blur if the event occurred on an element that is within + // the currently focused element + // See #10527, #12472 + if ( target.closest( activeElement ).length ) { + return; + } + + // Blur any element that currently has focus, see #4261 + $.ui.safeBlur( activeElement ); + }, + + _mouseStart: function( event ) { + + var o = this.options; + + //Create and append the visible helper + this.helper = this._createHelper( event ); + + this._addClass( this.helper, "ui-draggable-dragging" ); + + //Cache the helper size + this._cacheHelperProportions(); + + //If ddmanager is used for droppables, set the global draggable + if ( $.ui.ddmanager ) { + $.ui.ddmanager.current = this; + } + + /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ - //Cache the margins of the original element - this._cacheMargins(); - - //Store the helper's css position - this.cssPosition = this.helper.css( "position" ); - this.scrollParent = this.helper.scrollParent( true ); - this.offsetParent = this.helper.offsetParent(); - this.hasFixedAncestor = this.helper.parents().filter( function() { - return $( this ).css( "position" ) === "fixed"; - } ).length > 0; - - //The element's absolute position on the page minus margins - this.positionAbs = this.element.offset(); - this._refreshOffsets( event ); - - //Generate the original position - this.originalPosition = this.position = this._generatePosition( event, false ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); - - //Set a containment if given in the options - this._setContainment(); - - //Trigger event + callbacks - if ( this._trigger( "start", event ) === false ) { - this._clear(); - return false; - } - - //Recache the helper size - this._cacheHelperProportions(); - - //Prepare the droppable offsets - if ( $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); - } - - // Execute the drag once - this causes the helper not to be visible before getting its - // correct position - this._mouseDrag( event, true ); - - // If the ddmanager is used for droppables, inform the manager that dragging has started - // (see #5003) - if ( $.ui.ddmanager ) { - $.ui.ddmanager.dragStart( this, event ); - } - - return true; - }, - - _refreshOffsets: function( event ) { - this.offset = { - top: this.positionAbs.top - this.margins.top, - left: this.positionAbs.left - this.margins.left, - scroll: false, - parent: this._getParentOffset(), - relative: this._getRelativeOffset() - }; - - this.offset.click = { - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }; - }, - - _mouseDrag: function( event, noPropagation ) { - - // reset any necessary cached properties (see #5009) - if ( this.hasFixedAncestor ) { - this.offset.parent = this._getParentOffset(); - } - - //Compute the helpers position - this.position = this._generatePosition( event, true ); - this.positionAbs = this._convertPositionTo( "absolute" ); - - //Call plugins and callbacks and use the resulting position if something is returned - if ( !noPropagation ) { - var ui = this._uiHash(); - if ( this._trigger( "drag", event, ui ) === false ) { - this._mouseUp( new $.Event( "mouseup", event ) ); - return false; - } - this.position = ui.position; - } - - this.helper[ 0 ].style.left = this.position.left + "px"; - this.helper[ 0 ].style.top = this.position.top + "px"; - - if ( $.ui.ddmanager ) { - $.ui.ddmanager.drag( this, event ); - } - - return false; - }, - - _mouseStop: function( event ) { - - //If we are using droppables, inform the manager about the drop - var that = this, - dropped = false; - if ( $.ui.ddmanager && !this.options.dropBehaviour ) { - dropped = $.ui.ddmanager.drop( this, event ); - } - - //if a drop comes from outside (a sortable) - if ( this.dropped ) { - dropped = this.dropped; - this.dropped = false; - } - - if ( ( this.options.revert === "invalid" && !dropped ) || - ( this.options.revert === "valid" && dropped ) || - this.options.revert === true || ( $.isFunction( this.options.revert ) && - this.options.revert.call( this.element, dropped ) ) - ) { - $( this.helper ).animate( - this.originalPosition, - parseInt( this.options.revertDuration, 10 ), - function() { - if ( that._trigger( "stop", event ) !== false ) { - that._clear(); - } - } - ); - } else { - if ( this._trigger( "stop", event ) !== false ) { - this._clear(); - } - } - - return false; - }, - - _mouseUp: function( event ) { - this._unblockFrames(); - - // If the ddmanager is used for droppables, inform the manager that dragging has stopped - // (see #5003) - if ( $.ui.ddmanager ) { - $.ui.ddmanager.dragStop( this, event ); - } - - // Only need to focus if the event occurred on the draggable itself, see #10527 - if ( this.handleElement.is( event.target ) ) { - - // The interaction is over; whether or not the click resulted in a drag, - // focus the element - this.element.trigger( "focus" ); - } - - return $.ui.mouse.prototype._mouseUp.call( this, event ); - }, - - cancel: function() { - - if ( this.helper.is( ".ui-draggable-dragging" ) ) { - this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) ); - } else { - this._clear(); - } - - return this; - - }, - - _getHandle: function( event ) { - return this.options.handle ? - !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : - true; - }, - - _setHandleClassName: function() { - this.handleElement = this.options.handle ? - this.element.find( this.options.handle ) : this.element; - this._addClass( this.handleElement, "ui-draggable-handle" ); - }, - - _removeHandleClassName: function() { - this._removeClass( this.handleElement, "ui-draggable-handle" ); - }, - - _createHelper: function( event ) { - - var o = this.options, - helperIsFunction = $.isFunction( o.helper ), - helper = helperIsFunction ? - $( o.helper.apply( this.element[ 0 ], [ event ] ) ) : - ( o.helper === "clone" ? - this.element.clone().removeAttr( "id" ) : - this.element ); - - if ( !helper.parents( "body" ).length ) { - helper.appendTo( ( o.appendTo === "parent" ? - this.element[ 0 ].parentNode : - o.appendTo ) ); - } - - // Http://bugs.jqueryui.com/ticket/9446 - // a helper function can return the original element - // which wouldn't have been set to relative in _create - if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) { - this._setPositionRelative(); - } - - if ( helper[ 0 ] !== this.element[ 0 ] && - !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) { - helper.css( "position", "absolute" ); - } - - return helper; - - }, - - _setPositionRelative: function() { - if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) { - this.element[ 0 ].style.position = "relative"; - } - }, - - _adjustOffsetFromHelper: function( obj ) { - if ( typeof obj === "string" ) { - obj = obj.split( " " ); - } - if ( $.isArray( obj ) ) { - obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; - } - if ( "left" in obj ) { - this.offset.click.left = obj.left + this.margins.left; - } - if ( "right" in obj ) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ( "top" in obj ) { - this.offset.click.top = obj.top + this.margins.top; - } - if ( "bottom" in obj ) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _isRootNode: function( element ) { - return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ]; - }, - - _getParentOffset: function() { - - //Get the offsetParent and cache its position - var po = this.offsetParent.offset(), - document = this.document[ 0 ]; - - // This is a special case where we need to modify a offset calculated on start, since the - // following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the - // next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't - // the document, which means that the scroll is included in the initial calculation of the - // offset of the parent, and never recalculated upon drag - if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - if ( this._isRootNode( this.offsetParent[ 0 ] ) ) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), - left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) - }; - - }, - - _getRelativeOffset: function() { - if ( this.cssPosition !== "relative" ) { - return { top: 0, left: 0 }; - } - - var p = this.element.position(), - scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); - - return { - top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + - ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ), - left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + - ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 ) - }; - - }, - - _cacheMargins: function() { - this.margins = { - left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ), - top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ), - right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ), - bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 ) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var isUserScrollable, c, ce, - o = this.options, - document = this.document[ 0 ]; - - this.relativeContainer = null; - - if ( !o.containment ) { - this.containment = null; - return; - } - - if ( o.containment === "window" ) { - this.containment = [ - $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, - $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, - $( window ).scrollLeft() + $( window ).width() - - this.helperProportions.width - this.margins.left, - $( window ).scrollTop() + - ( $( window ).height() || document.body.parentNode.scrollHeight ) - - this.helperProportions.height - this.margins.top - ]; - return; - } - - if ( o.containment === "document" ) { - this.containment = [ - 0, - 0, - $( document ).width() - this.helperProportions.width - this.margins.left, - ( $( document ).height() || document.body.parentNode.scrollHeight ) - - this.helperProportions.height - this.margins.top - ]; - return; - } - - if ( o.containment.constructor === Array ) { - this.containment = o.containment; - return; - } - - if ( o.containment === "parent" ) { - o.containment = this.helper[ 0 ].parentNode; - } - - c = $( o.containment ); - ce = c[ 0 ]; - - if ( !ce ) { - return; - } - - isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) ); - - this.containment = [ - ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + - ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), - ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + - ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ), - ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - - this.helperProportions.width - - this.margins.left - - this.margins.right, - ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - - this.helperProportions.height - - this.margins.top - - this.margins.bottom - ]; - this.relativeContainer = c; - }, - - _convertPositionTo: function( d, pos ) { - - if ( !pos ) { - pos = this.position; - } - - var mod = d === "absolute" ? 1 : -1, - scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); - - return { - top: ( - - // The absolute mouse position - pos.top + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top * mod - - ( ( this.cssPosition === "fixed" ? - -this.offset.scroll.top : - ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod ) - ), - left: ( - - // The absolute mouse position - pos.left + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left * mod - - ( ( this.cssPosition === "fixed" ? - -this.offset.scroll.left : - ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod ) - ) - }; - - }, - - _generatePosition: function( event, constrainPosition ) { - - var containment, co, top, left, - o = this.options, - scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ), - pageX = event.pageX, - pageY = event.pageY; - - // Cache the scroll - if ( !scrollIsRootNode || !this.offset.scroll ) { - this.offset.scroll = { - top: this.scrollParent.scrollTop(), - left: this.scrollParent.scrollLeft() - }; - } - - /* + //Cache the margins of the original element + this._cacheMargins(); + + //Store the helper's css position + this.cssPosition = this.helper.css( "position" ); + this.scrollParent = this.helper.scrollParent( true ); + this.offsetParent = this.helper.offsetParent(); + this.hasFixedAncestor = this.helper.parents().filter( function() { + return $( this ).css( "position" ) === "fixed"; + } ).length > 0; + + //The element's absolute position on the page minus margins + this.positionAbs = this.element.offset(); + this._refreshOffsets( event ); + + //Generate the original position + this.originalPosition = this.position = this._generatePosition( event, false ); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + if ( o.cursorAt ) { + this._adjustOffsetFromHelper( o.cursorAt ); + } + + //Set a containment if given in the options + this._setContainment(); + + //Trigger event + callbacks + if ( this._trigger( "start", event ) === false ) { + this._clear(); + return false; + } + + //Recache the helper size + this._cacheHelperProportions(); + + //Prepare the droppable offsets + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + + // Execute the drag once - this causes the helper not to be visible before getting its + // correct position + this._mouseDrag( event, true ); + + // If the ddmanager is used for droppables, inform the manager that dragging has started + // (see #5003) + if ( $.ui.ddmanager ) { + $.ui.ddmanager.dragStart( this, event ); + } + + return true; + }, + + _refreshOffsets: function( event ) { + this.offset = { + top: this.positionAbs.top - this.margins.top, + left: this.positionAbs.left - this.margins.left, + scroll: false, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() + }; + + this.offset.click = { + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }; + }, + + _mouseDrag: function( event, noPropagation ) { + + // reset any necessary cached properties (see #5009) + if ( this.hasFixedAncestor ) { + this.offset.parent = this._getParentOffset(); + } + + //Compute the helpers position + this.position = this._generatePosition( event, true ); + this.positionAbs = this._convertPositionTo( "absolute" ); + + //Call plugins and callbacks and use the resulting position if something is returned + if ( !noPropagation ) { + var ui = this._uiHash(); + if ( this._trigger( "drag", event, ui ) === false ) { + this._mouseUp( new $.Event( "mouseup", event ) ); + return false; + } + this.position = ui.position; + } + + this.helper[ 0 ].style.left = this.position.left + "px"; + this.helper[ 0 ].style.top = this.position.top + "px"; + + if ( $.ui.ddmanager ) { + $.ui.ddmanager.drag( this, event ); + } + + return false; + }, + + _mouseStop: function( event ) { + + //If we are using droppables, inform the manager about the drop + var that = this, + dropped = false; + if ( $.ui.ddmanager && !this.options.dropBehaviour ) { + dropped = $.ui.ddmanager.drop( this, event ); + } + + //if a drop comes from outside (a sortable) + if ( this.dropped ) { + dropped = this.dropped; + this.dropped = false; + } + + if ( ( this.options.revert === "invalid" && !dropped ) || + ( this.options.revert === "valid" && dropped ) || + this.options.revert === true || ( typeof this.options.revert === "function" && + this.options.revert.call( this.element, dropped ) ) + ) { + $( this.helper ).animate( + this.originalPosition, + parseInt( this.options.revertDuration, 10 ), + function() { + if ( that._trigger( "stop", event ) !== false ) { + that._clear(); + } + } + ); + } else { + if ( this._trigger( "stop", event ) !== false ) { + this._clear(); + } + } + + return false; + }, + + _mouseUp: function( event ) { + this._unblockFrames(); + + // If the ddmanager is used for droppables, inform the manager that dragging has stopped + // (see #5003) + if ( $.ui.ddmanager ) { + $.ui.ddmanager.dragStop( this, event ); + } + + // Only need to focus if the event occurred on the draggable itself, see #10527 + if ( this.handleElement.is( event.target ) ) { + + // The interaction is over; whether or not the click resulted in a drag, + // focus the element + this.element.trigger( "focus" ); + } + + return $.ui.mouse.prototype._mouseUp.call( this, event ); + }, + + cancel: function() { + + if ( this.helper.is( ".ui-draggable-dragging" ) ) { + this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) ); + } else { + this._clear(); + } + + return this; + + }, + + _getHandle: function( event ) { + return this.options.handle ? + !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : + true; + }, + + _setHandleClassName: function() { + this.handleElement = this.options.handle ? + this.element.find( this.options.handle ) : this.element; + this._addClass( this.handleElement, "ui-draggable-handle" ); + }, + + _removeHandleClassName: function() { + this._removeClass( this.handleElement, "ui-draggable-handle" ); + }, + + _createHelper: function( event ) { + + var o = this.options, + helperIsFunction = typeof o.helper === "function", + helper = helperIsFunction ? + $( o.helper.apply( this.element[ 0 ], [ event ] ) ) : + ( o.helper === "clone" ? + this.element.clone().removeAttr( "id" ) : + this.element ); + + if ( !helper.parents( "body" ).length ) { + helper.appendTo( ( o.appendTo === "parent" ? + this.element[ 0 ].parentNode : + o.appendTo ) ); + } + + // Http://bugs.jqueryui.com/ticket/9446 + // a helper function can return the original element + // which wouldn't have been set to relative in _create + if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) { + this._setPositionRelative(); + } + + if ( helper[ 0 ] !== this.element[ 0 ] && + !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) { + helper.css( "position", "absolute" ); + } + + return helper; + + }, + + _setPositionRelative: function() { + if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) { + this.element[ 0 ].style.position = "relative"; + } + }, + + _adjustOffsetFromHelper: function( obj ) { + if ( typeof obj === "string" ) { + obj = obj.split( " " ); + } + if ( Array.isArray( obj ) ) { + obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; + } + if ( "left" in obj ) { + this.offset.click.left = obj.left + this.margins.left; + } + if ( "right" in obj ) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ( "top" in obj ) { + this.offset.click.top = obj.top + this.margins.top; + } + if ( "bottom" in obj ) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _isRootNode: function( element ) { + return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ]; + }, + + _getParentOffset: function() { + + //Get the offsetParent and cache its position + var po = this.offsetParent.offset(), + document = this.document[ 0 ]; + + // This is a special case where we need to modify a offset calculated on start, since the + // following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the + // next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't + // the document, which means that the scroll is included in the initial calculation of the + // offset of the parent, and never recalculated upon drag + if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document && + $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + if ( this._isRootNode( this.offsetParent[ 0 ] ) ) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), + left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) + }; + + }, + + _getRelativeOffset: function() { + if ( this.cssPosition !== "relative" ) { + return { top: 0, left: 0 }; + } + + var p = this.element.position(), + scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); + + return { + top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + + ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ), + left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + + ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 ) + }; + + }, + + _cacheMargins: function() { + this.margins = { + left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ), + top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ), + right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ), + bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 ) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var isUserScrollable, c, ce, + o = this.options, + document = this.document[ 0 ]; + + this.relativeContainer = null; + + if ( !o.containment ) { + this.containment = null; + return; + } + + if ( o.containment === "window" ) { + this.containment = [ + $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, + $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, + $( window ).scrollLeft() + $( window ).width() - + this.helperProportions.width - this.margins.left, + $( window ).scrollTop() + + ( $( window ).height() || document.body.parentNode.scrollHeight ) - + this.helperProportions.height - this.margins.top + ]; + return; + } + + if ( o.containment === "document" ) { + this.containment = [ + 0, + 0, + $( document ).width() - this.helperProportions.width - this.margins.left, + ( $( document ).height() || document.body.parentNode.scrollHeight ) - + this.helperProportions.height - this.margins.top + ]; + return; + } + + if ( o.containment.constructor === Array ) { + this.containment = o.containment; + return; + } + + if ( o.containment === "parent" ) { + o.containment = this.helper[ 0 ].parentNode; + } + + c = $( o.containment ); + ce = c[ 0 ]; + + if ( !ce ) { + return; + } + + isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) ); + + this.containment = [ + ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), + ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ), + ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - + ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - + ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - + this.helperProportions.width - + this.margins.left - + this.margins.right, + ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - + ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - + ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - + this.helperProportions.height - + this.margins.top - + this.margins.bottom + ]; + this.relativeContainer = c; + }, + + _convertPositionTo: function( d, pos ) { + + if ( !pos ) { + pos = this.position; + } + + var mod = d === "absolute" ? 1 : -1, + scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); + + return { + top: ( + + // The absolute mouse position + pos.top + + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.top * mod + + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.top * mod - + ( ( this.cssPosition === "fixed" ? + -this.offset.scroll.top : + ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod ) + ), + left: ( + + // The absolute mouse position + pos.left + + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.left * mod + + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.left * mod - + ( ( this.cssPosition === "fixed" ? + -this.offset.scroll.left : + ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod ) + ) + }; + + }, + + _generatePosition: function( event, constrainPosition ) { + + var containment, co, top, left, + o = this.options, + scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ), + pageX = event.pageX, + pageY = event.pageY; + + // Cache the scroll + if ( !scrollIsRootNode || !this.offset.scroll ) { + this.offset.scroll = { + top: this.scrollParent.scrollTop(), + left: this.scrollParent.scrollLeft() + }; + } + + /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ - // If we are not dragging yet, we won't check for options - if ( constrainPosition ) { - if ( this.containment ) { - if ( this.relativeContainer ) { - co = this.relativeContainer.offset(); - containment = [ - this.containment[ 0 ] + co.left, - this.containment[ 1 ] + co.top, - this.containment[ 2 ] + co.left, - this.containment[ 3 ] + co.top - ]; - } else { - containment = this.containment; - } - - if ( event.pageX - this.offset.click.left < containment[ 0 ] ) { - pageX = containment[ 0 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top < containment[ 1 ] ) { - pageY = containment[ 1 ] + this.offset.click.top; - } - if ( event.pageX - this.offset.click.left > containment[ 2 ] ) { - pageX = containment[ 2 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top > containment[ 3 ] ) { - pageY = containment[ 3 ] + this.offset.click.top; - } - } - - if ( o.grid ) { - - //Check for grid elements set to 0 to prevent divide by 0 error causing invalid - // argument errors in IE (see ticket #6950) - top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY - - this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY; - pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] || - top - this.offset.click.top > containment[ 3 ] ) ? - top : - ( ( top - this.offset.click.top >= containment[ 1 ] ) ? - top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top; - - left = o.grid[ 0 ] ? this.originalPageX + - Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] : - this.originalPageX; - pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] || - left - this.offset.click.left > containment[ 2 ] ) ? - left : - ( ( left - this.offset.click.left >= containment[ 0 ] ) ? - left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left; - } - - if ( o.axis === "y" ) { - pageX = this.originalPageX; - } - - if ( o.axis === "x" ) { - pageY = this.originalPageY; - } - } - - return { - top: ( - - // The absolute mouse position - pageY - - - // Click offset (relative to the element) - this.offset.click.top - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top + - ( this.cssPosition === "fixed" ? - -this.offset.scroll.top : - ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) - ), - left: ( - - // The absolute mouse position - pageX - - - // Click offset (relative to the element) - this.offset.click.left - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left + - ( this.cssPosition === "fixed" ? - -this.offset.scroll.left : - ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) - ) - }; - - }, - - _clear: function() { - this._removeClass( this.helper, "ui-draggable-dragging" ); - if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) { - this.helper.remove(); - } - this.helper = null; - this.cancelHelperRemoval = false; - if ( this.destroyOnClear ) { - this.destroy(); - } - }, - - // From now on bulk stuff - mainly helpers - - _trigger: function( type, event, ui ) { - ui = ui || this._uiHash(); - $.ui.plugin.call( this, type, [ event, ui, this ], true ); - - // Absolute position and offset (see #6884 ) have to be recalculated after plugins - if ( /^(drag|start|stop)/.test( type ) ) { - this.positionAbs = this._convertPositionTo( "absolute" ); - ui.offset = this.positionAbs; - } - return $.Widget.prototype._trigger.call( this, type, event, ui ); - }, - - plugins: {}, - - _uiHash: function() { - return { - helper: this.helper, - position: this.position, - originalPosition: this.originalPosition, - offset: this.positionAbs - }; - } - -} ); - -$.ui.plugin.add( "draggable", "connectToSortable", { - start: function( event, ui, draggable ) { - var uiSortable = $.extend( {}, ui, { - item: draggable.element - } ); - - draggable.sortables = []; - $( draggable.options.connectToSortable ).each( function() { - var sortable = $( this ).sortable( "instance" ); - - if ( sortable && !sortable.options.disabled ) { - draggable.sortables.push( sortable ); - - // RefreshPositions is called at drag start to refresh the containerCache - // which is used in drag. This ensures it's initialized and synchronized - // with any changes that might have happened on the page since initialization. - sortable.refreshPositions(); - sortable._trigger( "activate", event, uiSortable ); - } - } ); - }, - stop: function( event, ui, draggable ) { - var uiSortable = $.extend( {}, ui, { - item: draggable.element - } ); - - draggable.cancelHelperRemoval = false; - - $.each( draggable.sortables, function() { - var sortable = this; - - if ( sortable.isOver ) { - sortable.isOver = 0; - - // Allow this sortable to handle removing the helper - draggable.cancelHelperRemoval = true; - sortable.cancelHelperRemoval = false; - - // Use _storedCSS To restore properties in the sortable, - // as this also handles revert (#9675) since the draggable - // may have modified them in unexpected ways (#8809) - sortable._storedCSS = { - position: sortable.placeholder.css( "position" ), - top: sortable.placeholder.css( "top" ), - left: sortable.placeholder.css( "left" ) - }; - - sortable._mouseStop( event ); - - // Once drag has ended, the sortable should return to using - // its original helper, not the shared helper from draggable - sortable.options.helper = sortable.options._helper; - } else { - - // Prevent this Sortable from removing the helper. - // However, don't set the draggable to remove the helper - // either as another connected Sortable may yet handle the removal. - sortable.cancelHelperRemoval = true; - - sortable._trigger( "deactivate", event, uiSortable ); - } - } ); - }, - drag: function( event, ui, draggable ) { - $.each( draggable.sortables, function() { - var innermostIntersecting = false, - sortable = this; - - // Copy over variables that sortable's _intersectsWith uses - sortable.positionAbs = draggable.positionAbs; - sortable.helperProportions = draggable.helperProportions; - sortable.offset.click = draggable.offset.click; - - if ( sortable._intersectsWith( sortable.containerCache ) ) { - innermostIntersecting = true; - - $.each( draggable.sortables, function() { - - // Copy over variables that sortable's _intersectsWith uses - this.positionAbs = draggable.positionAbs; - this.helperProportions = draggable.helperProportions; - this.offset.click = draggable.offset.click; - - if ( this !== sortable && - this._intersectsWith( this.containerCache ) && - $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) { - innermostIntersecting = false; - } - - return innermostIntersecting; - } ); - } - - if ( innermostIntersecting ) { - - // If it intersects, we use a little isOver variable and set it once, - // so that the move-in stuff gets fired only once. - if ( !sortable.isOver ) { - sortable.isOver = 1; - - // Store draggable's parent in case we need to reappend to it later. - draggable._parent = ui.helper.parent(); - - sortable.currentItem = ui.helper - .appendTo( sortable.element ) - .data( "ui-sortable-item", true ); - - // Store helper option to later restore it - sortable.options._helper = sortable.options.helper; - - sortable.options.helper = function() { - return ui.helper[ 0 ]; - }; - - // Fire the start events of the sortable with our passed browser event, - // and our own helper (so it doesn't create a new one) - event.target = sortable.currentItem[ 0 ]; - sortable._mouseCapture( event, true ); - sortable._mouseStart( event, true, true ); - - // Because the browser event is way off the new appended portlet, - // modify necessary variables to reflect the changes - sortable.offset.click.top = draggable.offset.click.top; - sortable.offset.click.left = draggable.offset.click.left; - sortable.offset.parent.left -= draggable.offset.parent.left - - sortable.offset.parent.left; - sortable.offset.parent.top -= draggable.offset.parent.top - - sortable.offset.parent.top; - - draggable._trigger( "toSortable", event ); - - // Inform draggable that the helper is in a valid drop zone, - // used solely in the revert option to handle "valid/invalid". - draggable.dropped = sortable.element; - - // Need to refreshPositions of all sortables in the case that - // adding to one sortable changes the location of the other sortables (#9675) - $.each( draggable.sortables, function() { - this.refreshPositions(); - } ); - - // Hack so receive/update callbacks work (mostly) - draggable.currentItem = draggable.element; - sortable.fromOutside = draggable; - } - - if ( sortable.currentItem ) { - sortable._mouseDrag( event ); - - // Copy the sortable's position because the draggable's can potentially reflect - // a relative position, while sortable is always absolute, which the dragged - // element has now become. (#8809) - ui.position = sortable.position; - } - } else { - - // If it doesn't intersect with the sortable, and it intersected before, - // we fake the drag stop of the sortable, but make sure it doesn't remove - // the helper by using cancelHelperRemoval. - if ( sortable.isOver ) { - - sortable.isOver = 0; - sortable.cancelHelperRemoval = true; - - // Calling sortable's mouseStop would trigger a revert, - // so revert must be temporarily false until after mouseStop is called. - sortable.options._revert = sortable.options.revert; - sortable.options.revert = false; - - sortable._trigger( "out", event, sortable._uiHash( sortable ) ); - sortable._mouseStop( event, true ); - - // Restore sortable behaviors that were modfied - // when the draggable entered the sortable area (#9481) - sortable.options.revert = sortable.options._revert; - sortable.options.helper = sortable.options._helper; - - if ( sortable.placeholder ) { - sortable.placeholder.remove(); - } - - // Restore and recalculate the draggable's offset considering the sortable - // may have modified them in unexpected ways. (#8809, #10669) - ui.helper.appendTo( draggable._parent ); - draggable._refreshOffsets( event ); - ui.position = draggable._generatePosition( event, true ); - - draggable._trigger( "fromSortable", event ); - - // Inform draggable that the helper is no longer in a valid drop zone - draggable.dropped = false; - - // Need to refreshPositions of all sortables just in case removing - // from one sortable changes the location of other sortables (#9675) - $.each( draggable.sortables, function() { - this.refreshPositions(); - } ); - } - } - } ); - } -} ); - -$.ui.plugin.add( "draggable", "cursor", { - start: function( event, ui, instance ) { - var t = $( "body" ), - o = instance.options; - - if ( t.css( "cursor" ) ) { - o._cursor = t.css( "cursor" ); - } - t.css( "cursor", o.cursor ); - }, - stop: function( event, ui, instance ) { - var o = instance.options; - if ( o._cursor ) { - $( "body" ).css( "cursor", o._cursor ); - } - } -} ); - -$.ui.plugin.add( "draggable", "opacity", { - start: function( event, ui, instance ) { - var t = $( ui.helper ), - o = instance.options; - if ( t.css( "opacity" ) ) { - o._opacity = t.css( "opacity" ); - } - t.css( "opacity", o.opacity ); - }, - stop: function( event, ui, instance ) { - var o = instance.options; - if ( o._opacity ) { - $( ui.helper ).css( "opacity", o._opacity ); - } - } -} ); - -$.ui.plugin.add( "draggable", "scroll", { - start: function( event, ui, i ) { - if ( !i.scrollParentNotHidden ) { - i.scrollParentNotHidden = i.helper.scrollParent( false ); - } - - if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && - i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) { - i.overflowOffset = i.scrollParentNotHidden.offset(); - } - }, - drag: function( event, ui, i ) { - - var o = i.options, - scrolled = false, - scrollParent = i.scrollParentNotHidden[ 0 ], - document = i.document[ 0 ]; - - if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) { - if ( !o.axis || o.axis !== "x" ) { - if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < - o.scrollSensitivity ) { - scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed; - } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) { - scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed; - } - } - - if ( !o.axis || o.axis !== "y" ) { - if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < - o.scrollSensitivity ) { - scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed; - } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) { - scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed; - } - } - - } else { - - if ( !o.axis || o.axis !== "x" ) { - if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) { - scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed ); - } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) < - o.scrollSensitivity ) { - scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed ); - } - } - - if ( !o.axis || o.axis !== "y" ) { - if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) { - scrolled = $( document ).scrollLeft( - $( document ).scrollLeft() - o.scrollSpeed - ); - } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) < - o.scrollSensitivity ) { - scrolled = $( document ).scrollLeft( - $( document ).scrollLeft() + o.scrollSpeed - ); - } - } - - } - - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( i, event ); - } - - } -} ); - -$.ui.plugin.add( "draggable", "snap", { - start: function( event, ui, i ) { - - var o = i.options; - - i.snapElements = []; - - $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap ) - .each( function() { - var $t = $( this ), - $o = $t.offset(); - if ( this !== i.element[ 0 ] ) { - i.snapElements.push( { - item: this, - width: $t.outerWidth(), height: $t.outerHeight(), - top: $o.top, left: $o.left - } ); - } - } ); - - }, - drag: function( event, ui, inst ) { - - var ts, bs, ls, rs, l, r, t, b, i, first, - o = inst.options, - d = o.snapTolerance, - x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, - y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; - - for ( i = inst.snapElements.length - 1; i >= 0; i-- ) { - - l = inst.snapElements[ i ].left - inst.margins.left; - r = l + inst.snapElements[ i ].width; - t = inst.snapElements[ i ].top - inst.margins.top; - b = t + inst.snapElements[ i ].height; - - if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || - !$.contains( inst.snapElements[ i ].item.ownerDocument, - inst.snapElements[ i ].item ) ) { - if ( inst.snapElements[ i ].snapping ) { - ( inst.options.snap.release && - inst.options.snap.release.call( - inst.element, - event, - $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } ) - ) ); - } - inst.snapElements[ i ].snapping = false; - continue; - } - - if ( o.snapMode !== "inner" ) { - ts = Math.abs( t - y2 ) <= d; - bs = Math.abs( b - y1 ) <= d; - ls = Math.abs( l - x2 ) <= d; - rs = Math.abs( r - x1 ) <= d; - if ( ts ) { - ui.position.top = inst._convertPositionTo( "relative", { - top: t - inst.helperProportions.height, - left: 0 - } ).top; - } - if ( bs ) { - ui.position.top = inst._convertPositionTo( "relative", { - top: b, - left: 0 - } ).top; - } - if ( ls ) { - ui.position.left = inst._convertPositionTo( "relative", { - top: 0, - left: l - inst.helperProportions.width - } ).left; - } - if ( rs ) { - ui.position.left = inst._convertPositionTo( "relative", { - top: 0, - left: r - } ).left; - } - } - - first = ( ts || bs || ls || rs ); - - if ( o.snapMode !== "outer" ) { - ts = Math.abs( t - y1 ) <= d; - bs = Math.abs( b - y2 ) <= d; - ls = Math.abs( l - x1 ) <= d; - rs = Math.abs( r - x2 ) <= d; - if ( ts ) { - ui.position.top = inst._convertPositionTo( "relative", { - top: t, - left: 0 - } ).top; - } - if ( bs ) { - ui.position.top = inst._convertPositionTo( "relative", { - top: b - inst.helperProportions.height, - left: 0 - } ).top; - } - if ( ls ) { - ui.position.left = inst._convertPositionTo( "relative", { - top: 0, - left: l - } ).left; - } - if ( rs ) { - ui.position.left = inst._convertPositionTo( "relative", { - top: 0, - left: r - inst.helperProportions.width - } ).left; - } - } - - if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) { - ( inst.options.snap.snap && - inst.options.snap.snap.call( - inst.element, - event, - $.extend( inst._uiHash(), { - snapItem: inst.snapElements[ i ].item - } ) ) ); - } - inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first ); - - } - - } -} ); - -$.ui.plugin.add( "draggable", "stack", { - start: function( event, ui, instance ) { - var min, - o = instance.options, - group = $.makeArray( $( o.stack ) ).sort( function( a, b ) { - return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) - - ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 ); - } ); - - if ( !group.length ) { return; } - - min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0; - $( group ).each( function( i ) { - $( this ).css( "zIndex", min + i ); - } ); - this.css( "zIndex", ( min + group.length ) ); - } -} ); - -$.ui.plugin.add( "draggable", "zIndex", { - start: function( event, ui, instance ) { - var t = $( ui.helper ), - o = instance.options; - - if ( t.css( "zIndex" ) ) { - o._zIndex = t.css( "zIndex" ); - } - t.css( "zIndex", o.zIndex ); - }, - stop: function( event, ui, instance ) { - var o = instance.options; - - if ( o._zIndex ) { - $( ui.helper ).css( "zIndex", o._zIndex ); - } - } -} ); - -var widgetsDraggable = $.ui.draggable; - - -/*! - * jQuery UI Droppable 1.12.1 + // If we are not dragging yet, we won't check for options + if ( constrainPosition ) { + if ( this.containment ) { + if ( this.relativeContainer ) { + co = this.relativeContainer.offset(); + containment = [ + this.containment[ 0 ] + co.left, + this.containment[ 1 ] + co.top, + this.containment[ 2 ] + co.left, + this.containment[ 3 ] + co.top + ]; + } else { + containment = this.containment; + } + + if ( event.pageX - this.offset.click.left < containment[ 0 ] ) { + pageX = containment[ 0 ] + this.offset.click.left; + } + if ( event.pageY - this.offset.click.top < containment[ 1 ] ) { + pageY = containment[ 1 ] + this.offset.click.top; + } + if ( event.pageX - this.offset.click.left > containment[ 2 ] ) { + pageX = containment[ 2 ] + this.offset.click.left; + } + if ( event.pageY - this.offset.click.top > containment[ 3 ] ) { + pageY = containment[ 3 ] + this.offset.click.top; + } + } + + if ( o.grid ) { + + //Check for grid elements set to 0 to prevent divide by 0 error causing invalid + // argument errors in IE (see ticket #6950) + top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY - + this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY; + pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] || + top - this.offset.click.top > containment[ 3 ] ) ? + top : + ( ( top - this.offset.click.top >= containment[ 1 ] ) ? + top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top; + + left = o.grid[ 0 ] ? this.originalPageX + + Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] : + this.originalPageX; + pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] || + left - this.offset.click.left > containment[ 2 ] ) ? + left : + ( ( left - this.offset.click.left >= containment[ 0 ] ) ? + left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left; + } + + if ( o.axis === "y" ) { + pageX = this.originalPageX; + } + + if ( o.axis === "x" ) { + pageY = this.originalPageY; + } + } + + return { + top: ( + + // The absolute mouse position + pageY - + + // Click offset (relative to the element) + this.offset.click.top - + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.top - + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.top + + ( this.cssPosition === "fixed" ? + -this.offset.scroll.top : + ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) + ), + left: ( + + // The absolute mouse position + pageX - + + // Click offset (relative to the element) + this.offset.click.left - + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.left - + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.left + + ( this.cssPosition === "fixed" ? + -this.offset.scroll.left : + ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) + ) + }; + + }, + + _clear: function() { + this._removeClass( this.helper, "ui-draggable-dragging" ); + if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) { + this.helper.remove(); + } + this.helper = null; + this.cancelHelperRemoval = false; + if ( this.destroyOnClear ) { + this.destroy(); + } + }, + + // From now on bulk stuff - mainly helpers + + _trigger: function( type, event, ui ) { + ui = ui || this._uiHash(); + $.ui.plugin.call( this, type, [ event, ui, this ], true ); + + // Absolute position and offset (see #6884 ) have to be recalculated after plugins + if ( /^(drag|start|stop)/.test( type ) ) { + this.positionAbs = this._convertPositionTo( "absolute" ); + ui.offset = this.positionAbs; + } + return $.Widget.prototype._trigger.call( this, type, event, ui ); + }, + + plugins: {}, + + _uiHash: function() { + return { + helper: this.helper, + position: this.position, + originalPosition: this.originalPosition, + offset: this.positionAbs + }; + } + + } ); + + $.ui.plugin.add( "draggable", "connectToSortable", { + start: function( event, ui, draggable ) { + var uiSortable = $.extend( {}, ui, { + item: draggable.element + } ); + + draggable.sortables = []; + $( draggable.options.connectToSortable ).each( function() { + var sortable = $( this ).sortable( "instance" ); + + if ( sortable && !sortable.options.disabled ) { + draggable.sortables.push( sortable ); + + // RefreshPositions is called at drag start to refresh the containerCache + // which is used in drag. This ensures it's initialized and synchronized + // with any changes that might have happened on the page since initialization. + sortable.refreshPositions(); + sortable._trigger( "activate", event, uiSortable ); + } + } ); + }, + stop: function( event, ui, draggable ) { + var uiSortable = $.extend( {}, ui, { + item: draggable.element + } ); + + draggable.cancelHelperRemoval = false; + + $.each( draggable.sortables, function() { + var sortable = this; + + if ( sortable.isOver ) { + sortable.isOver = 0; + + // Allow this sortable to handle removing the helper + draggable.cancelHelperRemoval = true; + sortable.cancelHelperRemoval = false; + + // Use _storedCSS To restore properties in the sortable, + // as this also handles revert (#9675) since the draggable + // may have modified them in unexpected ways (#8809) + sortable._storedCSS = { + position: sortable.placeholder.css( "position" ), + top: sortable.placeholder.css( "top" ), + left: sortable.placeholder.css( "left" ) + }; + + sortable._mouseStop( event ); + + // Once drag has ended, the sortable should return to using + // its original helper, not the shared helper from draggable + sortable.options.helper = sortable.options._helper; + } else { + + // Prevent this Sortable from removing the helper. + // However, don't set the draggable to remove the helper + // either as another connected Sortable may yet handle the removal. + sortable.cancelHelperRemoval = true; + + sortable._trigger( "deactivate", event, uiSortable ); + } + } ); + }, + drag: function( event, ui, draggable ) { + $.each( draggable.sortables, function() { + var innermostIntersecting = false, + sortable = this; + + // Copy over variables that sortable's _intersectsWith uses + sortable.positionAbs = draggable.positionAbs; + sortable.helperProportions = draggable.helperProportions; + sortable.offset.click = draggable.offset.click; + + if ( sortable._intersectsWith( sortable.containerCache ) ) { + innermostIntersecting = true; + + $.each( draggable.sortables, function() { + + // Copy over variables that sortable's _intersectsWith uses + this.positionAbs = draggable.positionAbs; + this.helperProportions = draggable.helperProportions; + this.offset.click = draggable.offset.click; + + if ( this !== sortable && + this._intersectsWith( this.containerCache ) && + $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) { + innermostIntersecting = false; + } + + return innermostIntersecting; + } ); + } + + if ( innermostIntersecting ) { + + // If it intersects, we use a little isOver variable and set it once, + // so that the move-in stuff gets fired only once. + if ( !sortable.isOver ) { + sortable.isOver = 1; + + // Store draggable's parent in case we need to reappend to it later. + draggable._parent = ui.helper.parent(); + + sortable.currentItem = ui.helper + .appendTo( sortable.element ) + .data( "ui-sortable-item", true ); + + // Store helper option to later restore it + sortable.options._helper = sortable.options.helper; + + sortable.options.helper = function() { + return ui.helper[ 0 ]; + }; + + // Fire the start events of the sortable with our passed browser event, + // and our own helper (so it doesn't create a new one) + event.target = sortable.currentItem[ 0 ]; + sortable._mouseCapture( event, true ); + sortable._mouseStart( event, true, true ); + + // Because the browser event is way off the new appended portlet, + // modify necessary variables to reflect the changes + sortable.offset.click.top = draggable.offset.click.top; + sortable.offset.click.left = draggable.offset.click.left; + sortable.offset.parent.left -= draggable.offset.parent.left - + sortable.offset.parent.left; + sortable.offset.parent.top -= draggable.offset.parent.top - + sortable.offset.parent.top; + + draggable._trigger( "toSortable", event ); + + // Inform draggable that the helper is in a valid drop zone, + // used solely in the revert option to handle "valid/invalid". + draggable.dropped = sortable.element; + + // Need to refreshPositions of all sortables in the case that + // adding to one sortable changes the location of the other sortables (#9675) + $.each( draggable.sortables, function() { + this.refreshPositions(); + } ); + + // Hack so receive/update callbacks work (mostly) + draggable.currentItem = draggable.element; + sortable.fromOutside = draggable; + } + + if ( sortable.currentItem ) { + sortable._mouseDrag( event ); + + // Copy the sortable's position because the draggable's can potentially reflect + // a relative position, while sortable is always absolute, which the dragged + // element has now become. (#8809) + ui.position = sortable.position; + } + } else { + + // If it doesn't intersect with the sortable, and it intersected before, + // we fake the drag stop of the sortable, but make sure it doesn't remove + // the helper by using cancelHelperRemoval. + if ( sortable.isOver ) { + + sortable.isOver = 0; + sortable.cancelHelperRemoval = true; + + // Calling sortable's mouseStop would trigger a revert, + // so revert must be temporarily false until after mouseStop is called. + sortable.options._revert = sortable.options.revert; + sortable.options.revert = false; + + sortable._trigger( "out", event, sortable._uiHash( sortable ) ); + sortable._mouseStop( event, true ); + + // Restore sortable behaviors that were modfied + // when the draggable entered the sortable area (#9481) + sortable.options.revert = sortable.options._revert; + sortable.options.helper = sortable.options._helper; + + if ( sortable.placeholder ) { + sortable.placeholder.remove(); + } + + // Restore and recalculate the draggable's offset considering the sortable + // may have modified them in unexpected ways. (#8809, #10669) + ui.helper.appendTo( draggable._parent ); + draggable._refreshOffsets( event ); + ui.position = draggable._generatePosition( event, true ); + + draggable._trigger( "fromSortable", event ); + + // Inform draggable that the helper is no longer in a valid drop zone + draggable.dropped = false; + + // Need to refreshPositions of all sortables just in case removing + // from one sortable changes the location of other sortables (#9675) + $.each( draggable.sortables, function() { + this.refreshPositions(); + } ); + } + } + } ); + } + } ); + + $.ui.plugin.add( "draggable", "cursor", { + start: function( event, ui, instance ) { + var t = $( "body" ), + o = instance.options; + + if ( t.css( "cursor" ) ) { + o._cursor = t.css( "cursor" ); + } + t.css( "cursor", o.cursor ); + }, + stop: function( event, ui, instance ) { + var o = instance.options; + if ( o._cursor ) { + $( "body" ).css( "cursor", o._cursor ); + } + } + } ); + + $.ui.plugin.add( "draggable", "opacity", { + start: function( event, ui, instance ) { + var t = $( ui.helper ), + o = instance.options; + if ( t.css( "opacity" ) ) { + o._opacity = t.css( "opacity" ); + } + t.css( "opacity", o.opacity ); + }, + stop: function( event, ui, instance ) { + var o = instance.options; + if ( o._opacity ) { + $( ui.helper ).css( "opacity", o._opacity ); + } + } + } ); + + $.ui.plugin.add( "draggable", "scroll", { + start: function( event, ui, i ) { + if ( !i.scrollParentNotHidden ) { + i.scrollParentNotHidden = i.helper.scrollParent( false ); + } + + if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && + i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) { + i.overflowOffset = i.scrollParentNotHidden.offset(); + } + }, + drag: function( event, ui, i ) { + + var o = i.options, + scrolled = false, + scrollParent = i.scrollParentNotHidden[ 0 ], + document = i.document[ 0 ]; + + if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) { + if ( !o.axis || o.axis !== "x" ) { + if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < + o.scrollSensitivity ) { + scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed; + } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) { + scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed; + } + } + + if ( !o.axis || o.axis !== "y" ) { + if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < + o.scrollSensitivity ) { + scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed; + } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) { + scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed; + } + } + + } else { + + if ( !o.axis || o.axis !== "x" ) { + if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) { + scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed ); + } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) < + o.scrollSensitivity ) { + scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed ); + } + } + + if ( !o.axis || o.axis !== "y" ) { + if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) { + scrolled = $( document ).scrollLeft( + $( document ).scrollLeft() - o.scrollSpeed + ); + } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) < + o.scrollSensitivity ) { + scrolled = $( document ).scrollLeft( + $( document ).scrollLeft() + o.scrollSpeed + ); + } + } + + } + + if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( i, event ); + } + + } + } ); + + $.ui.plugin.add( "draggable", "snap", { + start: function( event, ui, i ) { + + var o = i.options; + + i.snapElements = []; + + $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap ) + .each( function() { + var $t = $( this ), + $o = $t.offset(); + if ( this !== i.element[ 0 ] ) { + i.snapElements.push( { + item: this, + width: $t.outerWidth(), height: $t.outerHeight(), + top: $o.top, left: $o.left + } ); + } + } ); + + }, + drag: function( event, ui, inst ) { + + var ts, bs, ls, rs, l, r, t, b, i, first, + o = inst.options, + d = o.snapTolerance, + x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, + y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; + + for ( i = inst.snapElements.length - 1; i >= 0; i-- ) { + + l = inst.snapElements[ i ].left - inst.margins.left; + r = l + inst.snapElements[ i ].width; + t = inst.snapElements[ i ].top - inst.margins.top; + b = t + inst.snapElements[ i ].height; + + if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || + !$.contains( inst.snapElements[ i ].item.ownerDocument, + inst.snapElements[ i ].item ) ) { + if ( inst.snapElements[ i ].snapping ) { + if ( inst.options.snap.release ) { + inst.options.snap.release.call( + inst.element, + event, + $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } ) + ); + } + } + inst.snapElements[ i ].snapping = false; + continue; + } + + if ( o.snapMode !== "inner" ) { + ts = Math.abs( t - y2 ) <= d; + bs = Math.abs( b - y1 ) <= d; + ls = Math.abs( l - x2 ) <= d; + rs = Math.abs( r - x1 ) <= d; + if ( ts ) { + ui.position.top = inst._convertPositionTo( "relative", { + top: t - inst.helperProportions.height, + left: 0 + } ).top; + } + if ( bs ) { + ui.position.top = inst._convertPositionTo( "relative", { + top: b, + left: 0 + } ).top; + } + if ( ls ) { + ui.position.left = inst._convertPositionTo( "relative", { + top: 0, + left: l - inst.helperProportions.width + } ).left; + } + if ( rs ) { + ui.position.left = inst._convertPositionTo( "relative", { + top: 0, + left: r + } ).left; + } + } + + first = ( ts || bs || ls || rs ); + + if ( o.snapMode !== "outer" ) { + ts = Math.abs( t - y1 ) <= d; + bs = Math.abs( b - y2 ) <= d; + ls = Math.abs( l - x1 ) <= d; + rs = Math.abs( r - x2 ) <= d; + if ( ts ) { + ui.position.top = inst._convertPositionTo( "relative", { + top: t, + left: 0 + } ).top; + } + if ( bs ) { + ui.position.top = inst._convertPositionTo( "relative", { + top: b - inst.helperProportions.height, + left: 0 + } ).top; + } + if ( ls ) { + ui.position.left = inst._convertPositionTo( "relative", { + top: 0, + left: l + } ).left; + } + if ( rs ) { + ui.position.left = inst._convertPositionTo( "relative", { + top: 0, + left: r - inst.helperProportions.width + } ).left; + } + } + + if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) { + if ( inst.options.snap.snap ) { + inst.options.snap.snap.call( + inst.element, + event, + $.extend( inst._uiHash(), { + snapItem: inst.snapElements[ i ].item + } ) ); + } + } + inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first ); + + } + + } + } ); + + $.ui.plugin.add( "draggable", "stack", { + start: function( event, ui, instance ) { + var min, + o = instance.options, + group = $.makeArray( $( o.stack ) ).sort( function( a, b ) { + return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) - + ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 ); + } ); + + if ( !group.length ) { + return; + } + + min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0; + $( group ).each( function( i ) { + $( this ).css( "zIndex", min + i ); + } ); + this.css( "zIndex", ( min + group.length ) ); + } + } ); + + $.ui.plugin.add( "draggable", "zIndex", { + start: function( event, ui, instance ) { + var t = $( ui.helper ), + o = instance.options; + + if ( t.css( "zIndex" ) ) { + o._zIndex = t.css( "zIndex" ); + } + t.css( "zIndex", o.zIndex ); + }, + stop: function( event, ui, instance ) { + var o = instance.options; + + if ( o._zIndex ) { + $( ui.helper ).css( "zIndex", o._zIndex ); + } + } + } ); + + var widgetsDraggable = $.ui.draggable; + + + /*! + * jQuery UI Droppable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -3256,473 +3274,475 @@ var widgetsDraggable = $.ui.draggable; //>>demos: http://jqueryui.com/droppable/ + $.widget( "ui.droppable", { + version: "1.13.0", + widgetEventPrefix: "drop", + options: { + accept: "*", + addClasses: true, + greedy: false, + scope: "default", + tolerance: "intersect", + + // Callbacks + activate: null, + deactivate: null, + drop: null, + out: null, + over: null + }, + _create: function() { + + var proportions, + o = this.options, + accept = o.accept; + + this.isover = false; + this.isout = true; + + this.accept = typeof accept === "function" ? accept : function( d ) { + return d.is( accept ); + }; + + this.proportions = function( /* valueToWrite */ ) { + if ( arguments.length ) { -$.widget( "ui.droppable", { - version: "1.12.1", - widgetEventPrefix: "drop", - options: { - accept: "*", - addClasses: true, - greedy: false, - scope: "default", - tolerance: "intersect", - - // Callbacks - activate: null, - deactivate: null, - drop: null, - out: null, - over: null - }, - _create: function() { - - var proportions, - o = this.options, - accept = o.accept; - - this.isover = false; - this.isout = true; - - this.accept = $.isFunction( accept ) ? accept : function( d ) { - return d.is( accept ); - }; - - this.proportions = function( /* valueToWrite */ ) { - if ( arguments.length ) { - - // Store the droppable's proportions - proportions = arguments[ 0 ]; - } else { - - // Retrieve or derive the droppable's proportions - return proportions ? - proportions : - proportions = { - width: this.element[ 0 ].offsetWidth, - height: this.element[ 0 ].offsetHeight - }; - } - }; - - this._addToManager( o.scope ); - - o.addClasses && this._addClass( "ui-droppable" ); - - }, - - _addToManager: function( scope ) { + // Store the droppable's proportions + proportions = arguments[ 0 ]; + } else { + + // Retrieve or derive the droppable's proportions + return proportions ? + proportions : + proportions = { + width: this.element[ 0 ].offsetWidth, + height: this.element[ 0 ].offsetHeight + }; + } + }; + + this._addToManager( o.scope ); - // Add the reference and positions to the manager - $.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || []; - $.ui.ddmanager.droppables[ scope ].push( this ); - }, + if ( o.addClasses ) { + this._addClass( "ui-droppable" ); + } - _splice: function( drop ) { - var i = 0; - for ( ; i < drop.length; i++ ) { - if ( drop[ i ] === this ) { - drop.splice( i, 1 ); - } - } - }, - - _destroy: function() { - var drop = $.ui.ddmanager.droppables[ this.options.scope ]; - - this._splice( drop ); - }, - - _setOption: function( key, value ) { - - if ( key === "accept" ) { - this.accept = $.isFunction( value ) ? value : function( d ) { - return d.is( value ); - }; - } else if ( key === "scope" ) { - var drop = $.ui.ddmanager.droppables[ this.options.scope ]; - - this._splice( drop ); - this._addToManager( value ); - } - - this._super( key, value ); - }, - - _activate: function( event ) { - var draggable = $.ui.ddmanager.current; - - this._addActiveClass(); - if ( draggable ) { - this._trigger( "activate", event, this.ui( draggable ) ); - } - }, - - _deactivate: function( event ) { - var draggable = $.ui.ddmanager.current; - - this._removeActiveClass(); - if ( draggable ) { - this._trigger( "deactivate", event, this.ui( draggable ) ); - } - }, - - _over: function( event ) { - - var draggable = $.ui.ddmanager.current; - - // Bail if draggable and droppable are same element - if ( !draggable || ( draggable.currentItem || - draggable.element )[ 0 ] === this.element[ 0 ] ) { - return; - } - - if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || - draggable.element ) ) ) { - this._addHoverClass(); - this._trigger( "over", event, this.ui( draggable ) ); - } - - }, - - _out: function( event ) { - - var draggable = $.ui.ddmanager.current; - - // Bail if draggable and droppable are same element - if ( !draggable || ( draggable.currentItem || - draggable.element )[ 0 ] === this.element[ 0 ] ) { - return; - } - - if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || - draggable.element ) ) ) { - this._removeHoverClass(); - this._trigger( "out", event, this.ui( draggable ) ); - } - - }, - - _drop: function( event, custom ) { - - var draggable = custom || $.ui.ddmanager.current, - childrenIntersection = false; - - // Bail if draggable and droppable are same element - if ( !draggable || ( draggable.currentItem || - draggable.element )[ 0 ] === this.element[ 0 ] ) { - return false; - } - - this.element - .find( ":data(ui-droppable)" ) - .not( ".ui-draggable-dragging" ) - .each( function() { - var inst = $( this ).droppable( "instance" ); - if ( - inst.options.greedy && - !inst.options.disabled && - inst.options.scope === draggable.options.scope && - inst.accept.call( - inst.element[ 0 ], ( draggable.currentItem || draggable.element ) - ) && - intersect( - draggable, - $.extend( inst, { offset: inst.element.offset() } ), - inst.options.tolerance, event - ) - ) { - childrenIntersection = true; - return false; } - } ); - if ( childrenIntersection ) { - return false; - } - - if ( this.accept.call( this.element[ 0 ], - ( draggable.currentItem || draggable.element ) ) ) { - this._removeActiveClass(); - this._removeHoverClass(); - - this._trigger( "drop", event, this.ui( draggable ) ); - return this.element; - } - - return false; - - }, - - ui: function( c ) { - return { - draggable: ( c.currentItem || c.element ), - helper: c.helper, - position: c.position, - offset: c.positionAbs - }; - }, - - // Extension points just to make backcompat sane and avoid duplicating logic - // TODO: Remove in 1.13 along with call to it below - _addHoverClass: function() { - this._addClass( "ui-droppable-hover" ); - }, - - _removeHoverClass: function() { - this._removeClass( "ui-droppable-hover" ); - }, - - _addActiveClass: function() { - this._addClass( "ui-droppable-active" ); - }, - - _removeActiveClass: function() { - this._removeClass( "ui-droppable-active" ); - } -} ); - -var intersect = $.ui.intersect = ( function() { - function isOverAxis( x, reference, size ) { - return ( x >= reference ) && ( x < ( reference + size ) ); - } - - return function( draggable, droppable, toleranceMode, event ) { - - if ( !droppable.offset ) { - return false; - } - - var x1 = ( draggable.positionAbs || - draggable.position.absolute ).left + draggable.margins.left, - y1 = ( draggable.positionAbs || - draggable.position.absolute ).top + draggable.margins.top, - x2 = x1 + draggable.helperProportions.width, - y2 = y1 + draggable.helperProportions.height, - l = droppable.offset.left, - t = droppable.offset.top, - r = l + droppable.proportions().width, - b = t + droppable.proportions().height; - - switch ( toleranceMode ) { - case "fit": - return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b ); - case "intersect": - return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half - x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half - t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half - y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half - case "pointer": - return isOverAxis( event.pageY, t, droppable.proportions().height ) && - isOverAxis( event.pageX, l, droppable.proportions().width ); - case "touch": - return ( - ( y1 >= t && y1 <= b ) || // Top edge touching - ( y2 >= t && y2 <= b ) || // Bottom edge touching - ( y1 < t && y2 > b ) // Surrounded vertically - ) && ( - ( x1 >= l && x1 <= r ) || // Left edge touching - ( x2 >= l && x2 <= r ) || // Right edge touching - ( x1 < l && x2 > r ) // Surrounded horizontally - ); - default: - return false; - } - }; -} )(); - -/* + }, + + _addToManager: function( scope ) { + + // Add the reference and positions to the manager + $.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || []; + $.ui.ddmanager.droppables[ scope ].push( this ); + }, + + _splice: function( drop ) { + var i = 0; + for ( ; i < drop.length; i++ ) { + if ( drop[ i ] === this ) { + drop.splice( i, 1 ); + } + } + }, + + _destroy: function() { + var drop = $.ui.ddmanager.droppables[ this.options.scope ]; + + this._splice( drop ); + }, + + _setOption: function( key, value ) { + + if ( key === "accept" ) { + this.accept = typeof value === "function" ? value : function( d ) { + return d.is( value ); + }; + } else if ( key === "scope" ) { + var drop = $.ui.ddmanager.droppables[ this.options.scope ]; + + this._splice( drop ); + this._addToManager( value ); + } + + this._super( key, value ); + }, + + _activate: function( event ) { + var draggable = $.ui.ddmanager.current; + + this._addActiveClass(); + if ( draggable ) { + this._trigger( "activate", event, this.ui( draggable ) ); + } + }, + + _deactivate: function( event ) { + var draggable = $.ui.ddmanager.current; + + this._removeActiveClass(); + if ( draggable ) { + this._trigger( "deactivate", event, this.ui( draggable ) ); + } + }, + + _over: function( event ) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if ( !draggable || ( draggable.currentItem || + draggable.element )[ 0 ] === this.element[ 0 ] ) { + return; + } + + if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || + draggable.element ) ) ) { + this._addHoverClass(); + this._trigger( "over", event, this.ui( draggable ) ); + } + + }, + + _out: function( event ) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if ( !draggable || ( draggable.currentItem || + draggable.element )[ 0 ] === this.element[ 0 ] ) { + return; + } + + if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || + draggable.element ) ) ) { + this._removeHoverClass(); + this._trigger( "out", event, this.ui( draggable ) ); + } + + }, + + _drop: function( event, custom ) { + + var draggable = custom || $.ui.ddmanager.current, + childrenIntersection = false; + + // Bail if draggable and droppable are same element + if ( !draggable || ( draggable.currentItem || + draggable.element )[ 0 ] === this.element[ 0 ] ) { + return false; + } + + this.element + .find( ":data(ui-droppable)" ) + .not( ".ui-draggable-dragging" ) + .each( function() { + var inst = $( this ).droppable( "instance" ); + if ( + inst.options.greedy && + !inst.options.disabled && + inst.options.scope === draggable.options.scope && + inst.accept.call( + inst.element[ 0 ], ( draggable.currentItem || draggable.element ) + ) && + $.ui.intersect( + draggable, + $.extend( inst, { offset: inst.element.offset() } ), + inst.options.tolerance, event + ) + ) { + childrenIntersection = true; + return false; + } + } ); + if ( childrenIntersection ) { + return false; + } + + if ( this.accept.call( this.element[ 0 ], + ( draggable.currentItem || draggable.element ) ) ) { + this._removeActiveClass(); + this._removeHoverClass(); + + this._trigger( "drop", event, this.ui( draggable ) ); + return this.element; + } + + return false; + + }, + + ui: function( c ) { + return { + draggable: ( c.currentItem || c.element ), + helper: c.helper, + position: c.position, + offset: c.positionAbs + }; + }, + + // Extension points just to make backcompat sane and avoid duplicating logic + // TODO: Remove in 1.14 along with call to it below + _addHoverClass: function() { + this._addClass( "ui-droppable-hover" ); + }, + + _removeHoverClass: function() { + this._removeClass( "ui-droppable-hover" ); + }, + + _addActiveClass: function() { + this._addClass( "ui-droppable-active" ); + }, + + _removeActiveClass: function() { + this._removeClass( "ui-droppable-active" ); + } + } ); + + $.ui.intersect = ( function() { + function isOverAxis( x, reference, size ) { + return ( x >= reference ) && ( x < ( reference + size ) ); + } + + return function( draggable, droppable, toleranceMode, event ) { + + if ( !droppable.offset ) { + return false; + } + + var x1 = ( draggable.positionAbs || + draggable.position.absolute ).left + draggable.margins.left, + y1 = ( draggable.positionAbs || + draggable.position.absolute ).top + draggable.margins.top, + x2 = x1 + draggable.helperProportions.width, + y2 = y1 + draggable.helperProportions.height, + l = droppable.offset.left, + t = droppable.offset.top, + r = l + droppable.proportions().width, + b = t + droppable.proportions().height; + + switch ( toleranceMode ) { + case "fit": + return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b ); + case "intersect": + return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half + x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half + t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half + y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half + case "pointer": + return isOverAxis( event.pageY, t, droppable.proportions().height ) && + isOverAxis( event.pageX, l, droppable.proportions().width ); + case "touch": + return ( + ( y1 >= t && y1 <= b ) || // Top edge touching + ( y2 >= t && y2 <= b ) || // Bottom edge touching + ( y1 < t && y2 > b ) // Surrounded vertically + ) && ( + ( x1 >= l && x1 <= r ) || // Left edge touching + ( x2 >= l && x2 <= r ) || // Right edge touching + ( x1 < l && x2 > r ) // Surrounded horizontally + ); + default: + return false; + } + }; + } )(); + + /* This manager tracks offsets of draggables and droppables */ -$.ui.ddmanager = { - current: null, - droppables: { "default": [] }, - prepareOffsets: function( t, event ) { - - var i, j, - m = $.ui.ddmanager.droppables[ t.options.scope ] || [], - type = event ? event.type : null, // workaround for #2317 - list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack(); - - droppablesLoop: for ( i = 0; i < m.length; i++ ) { - - // No disabled and non-accepted - if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], - ( t.currentItem || t.element ) ) ) ) { - continue; - } - - // Filter out elements in the current dragged item - for ( j = 0; j < list.length; j++ ) { - if ( list[ j ] === m[ i ].element[ 0 ] ) { - m[ i ].proportions().height = 0; - continue droppablesLoop; - } - } - - m[ i ].visible = m[ i ].element.css( "display" ) !== "none"; - if ( !m[ i ].visible ) { - continue; - } - - // Activate the droppable if used directly from draggables - if ( type === "mousedown" ) { - m[ i ]._activate.call( m[ i ], event ); - } - - m[ i ].offset = m[ i ].element.offset(); - m[ i ].proportions( { - width: m[ i ].element[ 0 ].offsetWidth, - height: m[ i ].element[ 0 ].offsetHeight - } ); - - } - - }, - drop: function( draggable, event ) { - - var dropped = false; - - // Create a copy of the droppables in case the list changes during the drop (#9116) - $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() { - - if ( !this.options ) { - return; - } - if ( !this.options.disabled && this.visible && - intersect( draggable, this, this.options.tolerance, event ) ) { - dropped = this._drop.call( this, event ) || dropped; - } - - if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], - ( draggable.currentItem || draggable.element ) ) ) { - this.isout = true; - this.isover = false; - this._deactivate.call( this, event ); - } - - } ); - return dropped; - - }, - dragStart: function( draggable, event ) { - - // Listen for scrolling so that if the dragging causes scrolling the position of the - // droppables can be recalculated (see #5003) - draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() { - if ( !draggable.options.refreshPositions ) { - $.ui.ddmanager.prepareOffsets( draggable, event ); - } - } ); - }, - drag: function( draggable, event ) { - - // If you have a highly dynamic page, you might try this option. It renders positions - // every time you move the mouse. - if ( draggable.options.refreshPositions ) { - $.ui.ddmanager.prepareOffsets( draggable, event ); - } - - // Run through all droppables and check their positions based on specific tolerance options - $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() { - - if ( this.options.disabled || this.greedyChild || !this.visible ) { - return; - } - - var parentInstance, scope, parent, - intersects = intersect( draggable, this, this.options.tolerance, event ), - c = !intersects && this.isover ? - "isout" : - ( intersects && !this.isover ? "isover" : null ); - if ( !c ) { - return; - } - - if ( this.options.greedy ) { - - // find droppable parents with same scope - scope = this.options.scope; - parent = this.element.parents( ":data(ui-droppable)" ).filter( function() { - return $( this ).droppable( "instance" ).options.scope === scope; - } ); - - if ( parent.length ) { - parentInstance = $( parent[ 0 ] ).droppable( "instance" ); - parentInstance.greedyChild = ( c === "isover" ); - } - } - - // We just moved into a greedy child - if ( parentInstance && c === "isover" ) { - parentInstance.isover = false; - parentInstance.isout = true; - parentInstance._out.call( parentInstance, event ); - } - - this[ c ] = true; - this[ c === "isout" ? "isover" : "isout" ] = false; - this[ c === "isover" ? "_over" : "_out" ].call( this, event ); - - // We just moved out of a greedy child - if ( parentInstance && c === "isout" ) { - parentInstance.isout = false; - parentInstance.isover = true; - parentInstance._over.call( parentInstance, event ); - } - } ); - - }, - dragStop: function( draggable, event ) { - draggable.element.parentsUntil( "body" ).off( "scroll.droppable" ); - - // Call prepareOffsets one final time since IE does not fire return scroll events when - // overflow was caused by drag (see #5003) - if ( !draggable.options.refreshPositions ) { - $.ui.ddmanager.prepareOffsets( draggable, event ); - } - } -}; + $.ui.ddmanager = { + current: null, + droppables: { "default": [] }, + prepareOffsets: function( t, event ) { + + var i, j, + m = $.ui.ddmanager.droppables[ t.options.scope ] || [], + type = event ? event.type : null, // workaround for #2317 + list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack(); + + droppablesLoop: for ( i = 0; i < m.length; i++ ) { + + // No disabled and non-accepted + if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], + ( t.currentItem || t.element ) ) ) ) { + continue; + } + + // Filter out elements in the current dragged item + for ( j = 0; j < list.length; j++ ) { + if ( list[ j ] === m[ i ].element[ 0 ] ) { + m[ i ].proportions().height = 0; + continue droppablesLoop; + } + } + + m[ i ].visible = m[ i ].element.css( "display" ) !== "none"; + if ( !m[ i ].visible ) { + continue; + } + + // Activate the droppable if used directly from draggables + if ( type === "mousedown" ) { + m[ i ]._activate.call( m[ i ], event ); + } + + m[ i ].offset = m[ i ].element.offset(); + m[ i ].proportions( { + width: m[ i ].element[ 0 ].offsetWidth, + height: m[ i ].element[ 0 ].offsetHeight + } ); + + } + + }, + drop: function( draggable, event ) { + + var dropped = false; + + // Create a copy of the droppables in case the list changes during the drop (#9116) + $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() { + + if ( !this.options ) { + return; + } + if ( !this.options.disabled && this.visible && + $.ui.intersect( draggable, this, this.options.tolerance, event ) ) { + dropped = this._drop.call( this, event ) || dropped; + } + + if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], + ( draggable.currentItem || draggable.element ) ) ) { + this.isout = true; + this.isover = false; + this._deactivate.call( this, event ); + } + + } ); + return dropped; + + }, + dragStart: function( draggable, event ) { + + // Listen for scrolling so that if the dragging causes scrolling the position of the + // droppables can be recalculated (see #5003) + draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() { + if ( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + } ); + }, + drag: function( draggable, event ) { + + // If you have a highly dynamic page, you might try this option. It renders positions + // every time you move the mouse. + if ( draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + + // Run through all droppables and check their positions based on specific tolerance options + $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() { + + if ( this.options.disabled || this.greedyChild || !this.visible ) { + return; + } + + var parentInstance, scope, parent, + intersects = $.ui.intersect( draggable, this, this.options.tolerance, event ), + c = !intersects && this.isover ? + "isout" : + ( intersects && !this.isover ? "isover" : null ); + if ( !c ) { + return; + } + + if ( this.options.greedy ) { + + // find droppable parents with same scope + scope = this.options.scope; + parent = this.element.parents( ":data(ui-droppable)" ).filter( function() { + return $( this ).droppable( "instance" ).options.scope === scope; + } ); + + if ( parent.length ) { + parentInstance = $( parent[ 0 ] ).droppable( "instance" ); + parentInstance.greedyChild = ( c === "isover" ); + } + } + + // We just moved into a greedy child + if ( parentInstance && c === "isover" ) { + parentInstance.isover = false; + parentInstance.isout = true; + parentInstance._out.call( parentInstance, event ); + } + + this[ c ] = true; + this[ c === "isout" ? "isover" : "isout" ] = false; + this[ c === "isover" ? "_over" : "_out" ].call( this, event ); + + // We just moved out of a greedy child + if ( parentInstance && c === "isout" ) { + parentInstance.isout = false; + parentInstance.isover = true; + parentInstance._over.call( parentInstance, event ); + } + } ); + + }, + dragStop: function( draggable, event ) { + draggable.element.parentsUntil( "body" ).off( "scroll.droppable" ); + + // Call prepareOffsets one final time since IE does not fire return scroll events when + // overflow was caused by drag (see #5003) + if ( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + } + }; // DEPRECATED // TODO: switch return back to widget declaration at top of file when this is removed -if ( $.uiBackCompat !== false ) { - - // Backcompat for activeClass and hoverClass options - $.widget( "ui.droppable", $.ui.droppable, { - options: { - hoverClass: false, - activeClass: false - }, - _addActiveClass: function() { - this._super(); - if ( this.options.activeClass ) { - this.element.addClass( this.options.activeClass ); - } - }, - _removeActiveClass: function() { - this._super(); - if ( this.options.activeClass ) { - this.element.removeClass( this.options.activeClass ); - } - }, - _addHoverClass: function() { - this._super(); - if ( this.options.hoverClass ) { - this.element.addClass( this.options.hoverClass ); - } - }, - _removeHoverClass: function() { - this._super(); - if ( this.options.hoverClass ) { - this.element.removeClass( this.options.hoverClass ); - } - } - } ); -} - -var widgetsDroppable = $.ui.droppable; - - -/*! - * jQuery UI Resizable 1.12.1 + if ( $.uiBackCompat !== false ) { + + // Backcompat for activeClass and hoverClass options + $.widget( "ui.droppable", $.ui.droppable, { + options: { + hoverClass: false, + activeClass: false + }, + _addActiveClass: function() { + this._super(); + if ( this.options.activeClass ) { + this.element.addClass( this.options.activeClass ); + } + }, + _removeActiveClass: function() { + this._super(); + if ( this.options.activeClass ) { + this.element.removeClass( this.options.activeClass ); + } + }, + _addHoverClass: function() { + this._super(); + if ( this.options.hoverClass ) { + this.element.addClass( this.options.hoverClass ); + } + }, + _removeHoverClass: function() { + this._super(); + if ( this.options.hoverClass ) { + this.element.removeClass( this.options.hoverClass ); + } + } + } ); + } + + var widgetsDroppable = $.ui.droppable; + + + /*! + * jQuery UI Resizable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -3740,1173 +3760,1187 @@ var widgetsDroppable = $.ui.droppable; //>>css.theme: ../../themes/base/theme.css + $.widget( "ui.resizable", $.ui.mouse, { + version: "1.13.0", + widgetEventPrefix: "resize", + options: { + alsoResize: false, + animate: false, + animateDuration: "slow", + animateEasing: "swing", + aspectRatio: false, + autoHide: false, + classes: { + "ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se" + }, + containment: false, + ghost: false, + grid: false, + handles: "e,s,se", + helper: false, + maxHeight: null, + maxWidth: null, + minHeight: 10, + minWidth: 10, + + // See #7960 + zIndex: 90, + + // Callbacks + resize: null, + start: null, + stop: null + }, + + _num: function( value ) { + return parseFloat( value ) || 0; + }, + + _isNumber: function( value ) { + return !isNaN( parseFloat( value ) ); + }, + + _hasScroll: function( el, a ) { + + if ( $( el ).css( "overflow" ) === "hidden" ) { + return false; + } + + var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", + has = false; + + if ( el[ scroll ] > 0 ) { + return true; + } + + // TODO: determine which cases actually cause this to happen + // if the element doesn't have the scroll set, see if it's possible to + // set the scroll + try { + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + } catch ( e ) { + + // `el` might be a string, then setting `scroll` will throw + // an error in strict mode; ignore it. + } + return has; + }, + + _create: function() { + + var margins, + o = this.options, + that = this; + this._addClass( "ui-resizable" ); + + $.extend( this, { + _aspectRatio: !!( o.aspectRatio ), + aspectRatio: o.aspectRatio, + originalElement: this.element, + _proportionallyResizeElements: [], + _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null + } ); + + // Wrap the element if it cannot hold child nodes + if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) { + + this.element.wrap( + $( "<div class='ui-wrapper'></div>" ).css( { + overflow: "hidden", + position: this.element.css( "position" ), + width: this.element.outerWidth(), + height: this.element.outerHeight(), + top: this.element.css( "top" ), + left: this.element.css( "left" ) + } ) + ); + + this.element = this.element.parent().data( + "ui-resizable", this.element.resizable( "instance" ) + ); + + this.elementIsWrapper = true; + + margins = { + marginTop: this.originalElement.css( "marginTop" ), + marginRight: this.originalElement.css( "marginRight" ), + marginBottom: this.originalElement.css( "marginBottom" ), + marginLeft: this.originalElement.css( "marginLeft" ) + }; + + this.element.css( margins ); + this.originalElement.css( "margin", 0 ); + + // support: Safari + // Prevent Safari textarea resize + this.originalResizeStyle = this.originalElement.css( "resize" ); + this.originalElement.css( "resize", "none" ); + + this._proportionallyResizeElements.push( this.originalElement.css( { + position: "static", + zoom: 1, + display: "block" + } ) ); + + // Support: IE9 + // avoid IE jump (hard set the margin) + this.originalElement.css( margins ); + + this._proportionallyResize(); + } + + this._setupHandles(); + + if ( o.autoHide ) { + $( this.element ) + .on( "mouseenter", function() { + if ( o.disabled ) { + return; + } + that._removeClass( "ui-resizable-autohide" ); + that._handles.show(); + } ) + .on( "mouseleave", function() { + if ( o.disabled ) { + return; + } + if ( !that.resizing ) { + that._addClass( "ui-resizable-autohide" ); + that._handles.hide(); + } + } ); + } + + this._mouseInit(); + }, + + _destroy: function() { + + this._mouseDestroy(); + this._addedHandles.remove(); + + var wrapper, + _destroy = function( exp ) { + $( exp ) + .removeData( "resizable" ) + .removeData( "ui-resizable" ) + .off( ".resizable" ); + }; + + // TODO: Unwrap at same DOM position + if ( this.elementIsWrapper ) { + _destroy( this.element ); + wrapper = this.element; + this.originalElement.css( { + position: wrapper.css( "position" ), + width: wrapper.outerWidth(), + height: wrapper.outerHeight(), + top: wrapper.css( "top" ), + left: wrapper.css( "left" ) + } ).insertAfter( wrapper ); + wrapper.remove(); + } + + this.originalElement.css( "resize", this.originalResizeStyle ); + _destroy( this.originalElement ); + + return this; + }, + + _setOption: function( key, value ) { + this._super( key, value ); + + switch ( key ) { + case "handles": + this._removeHandles(); + this._setupHandles(); + break; + case "aspectRatio": + this._aspectRatio = !!value; + break; + default: + break; + } + }, + + _setupHandles: function() { + var o = this.options, handle, i, n, hname, axis, that = this; + this.handles = o.handles || + ( !$( ".ui-resizable-handle", this.element ).length ? + "e,s,se" : { + n: ".ui-resizable-n", + e: ".ui-resizable-e", + s: ".ui-resizable-s", + w: ".ui-resizable-w", + se: ".ui-resizable-se", + sw: ".ui-resizable-sw", + ne: ".ui-resizable-ne", + nw: ".ui-resizable-nw" + } ); + + this._handles = $(); + this._addedHandles = $(); + if ( this.handles.constructor === String ) { + + if ( this.handles === "all" ) { + this.handles = "n,e,s,w,se,sw,ne,nw"; + } + + n = this.handles.split( "," ); + this.handles = {}; + + for ( i = 0; i < n.length; i++ ) { + + handle = String.prototype.trim.call( n[ i ] ); + hname = "ui-resizable-" + handle; + axis = $( "<div>" ); + this._addClass( axis, "ui-resizable-handle " + hname ); + + axis.css( { zIndex: o.zIndex } ); + + this.handles[ handle ] = ".ui-resizable-" + handle; + if ( !this.element.children( this.handles[ handle ] ).length ) { + this.element.append( axis ); + this._addedHandles = this._addedHandles.add( axis ); + } + } + + } + + this._renderAxis = function( target ) { + + var i, axis, padPos, padWrapper; + + target = target || this.element; + + for ( i in this.handles ) { + + if ( this.handles[ i ].constructor === String ) { + this.handles[ i ] = this.element.children( this.handles[ i ] ).first().show(); + } else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) { + this.handles[ i ] = $( this.handles[ i ] ); + this._on( this.handles[ i ], { "mousedown": that._mouseDown } ); + } + + if ( this.elementIsWrapper && + this.originalElement[ 0 ] + .nodeName + .match( /^(textarea|input|select|button)$/i ) ) { + axis = $( this.handles[ i ], this.element ); + + padWrapper = /sw|ne|nw|se|n|s/.test( i ) ? + axis.outerHeight() : + axis.outerWidth(); + + padPos = [ "padding", + /ne|nw|n/.test( i ) ? "Top" : + /se|sw|s/.test( i ) ? "Bottom" : + /^e$/.test( i ) ? "Right" : "Left" ].join( "" ); + + target.css( padPos, padWrapper ); + + this._proportionallyResize(); + } + + this._handles = this._handles.add( this.handles[ i ] ); + } + }; + + // TODO: make renderAxis a prototype function + this._renderAxis( this.element ); + + this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) ); + this._handles.disableSelection(); + + this._handles.on( "mouseover", function() { + if ( !that.resizing ) { + if ( this.className ) { + axis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i ); + } + that.axis = axis && axis[ 1 ] ? axis[ 1 ] : "se"; + } + } ); + + if ( o.autoHide ) { + this._handles.hide(); + this._addClass( "ui-resizable-autohide" ); + } + }, + + _removeHandles: function() { + this._addedHandles.remove(); + }, -$.widget( "ui.resizable", $.ui.mouse, { - version: "1.12.1", - widgetEventPrefix: "resize", - options: { - alsoResize: false, - animate: false, - animateDuration: "slow", - animateEasing: "swing", - aspectRatio: false, - autoHide: false, - classes: { - "ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se" - }, - containment: false, - ghost: false, - grid: false, - handles: "e,s,se", - helper: false, - maxHeight: null, - maxWidth: null, - minHeight: 10, - minWidth: 10, - - // See #7960 - zIndex: 90, - - // Callbacks - resize: null, - start: null, - stop: null - }, - - _num: function( value ) { - return parseFloat( value ) || 0; - }, - - _isNumber: function( value ) { - return !isNaN( parseFloat( value ) ); - }, - - _hasScroll: function( el, a ) { - - if ( $( el ).css( "overflow" ) === "hidden" ) { - return false; - } - - var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", - has = false; - - if ( el[ scroll ] > 0 ) { - return true; - } - - // TODO: determine which cases actually cause this to happen - // if the element doesn't have the scroll set, see if it's possible to - // set the scroll - el[ scroll ] = 1; - has = ( el[ scroll ] > 0 ); - el[ scroll ] = 0; - return has; - }, - - _create: function() { - - var margins, - o = this.options, - that = this; - this._addClass( "ui-resizable" ); - - $.extend( this, { - _aspectRatio: !!( o.aspectRatio ), - aspectRatio: o.aspectRatio, - originalElement: this.element, - _proportionallyResizeElements: [], - _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null - } ); - - // Wrap the element if it cannot hold child nodes - if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) { - - this.element.wrap( - $( "<div class='ui-wrapper' style='overflow: hidden;'></div>" ).css( { - position: this.element.css( "position" ), - width: this.element.outerWidth(), - height: this.element.outerHeight(), - top: this.element.css( "top" ), - left: this.element.css( "left" ) - } ) - ); - - this.element = this.element.parent().data( - "ui-resizable", this.element.resizable( "instance" ) - ); - - this.elementIsWrapper = true; - - margins = { - marginTop: this.originalElement.css( "marginTop" ), - marginRight: this.originalElement.css( "marginRight" ), - marginBottom: this.originalElement.css( "marginBottom" ), - marginLeft: this.originalElement.css( "marginLeft" ) - }; - - this.element.css( margins ); - this.originalElement.css( "margin", 0 ); - - // support: Safari - // Prevent Safari textarea resize - this.originalResizeStyle = this.originalElement.css( "resize" ); - this.originalElement.css( "resize", "none" ); - - this._proportionallyResizeElements.push( this.originalElement.css( { - position: "static", - zoom: 1, - display: "block" - } ) ); - - // Support: IE9 - // avoid IE jump (hard set the margin) - this.originalElement.css( margins ); - - this._proportionallyResize(); - } - - this._setupHandles(); - - if ( o.autoHide ) { - $( this.element ) - .on( "mouseenter", function() { - if ( o.disabled ) { - return; - } - that._removeClass( "ui-resizable-autohide" ); - that._handles.show(); - } ) - .on( "mouseleave", function() { - if ( o.disabled ) { - return; - } - if ( !that.resizing ) { - that._addClass( "ui-resizable-autohide" ); - that._handles.hide(); - } - } ); - } - - this._mouseInit(); - }, - - _destroy: function() { - - this._mouseDestroy(); - - var wrapper, - _destroy = function( exp ) { - $( exp ) - .removeData( "resizable" ) - .removeData( "ui-resizable" ) - .off( ".resizable" ) - .find( ".ui-resizable-handle" ) - .remove(); - }; - - // TODO: Unwrap at same DOM position - if ( this.elementIsWrapper ) { - _destroy( this.element ); - wrapper = this.element; - this.originalElement.css( { - position: wrapper.css( "position" ), - width: wrapper.outerWidth(), - height: wrapper.outerHeight(), - top: wrapper.css( "top" ), - left: wrapper.css( "left" ) - } ).insertAfter( wrapper ); - wrapper.remove(); - } - - this.originalElement.css( "resize", this.originalResizeStyle ); - _destroy( this.originalElement ); - - return this; - }, - - _setOption: function( key, value ) { - this._super( key, value ); - - switch ( key ) { - case "handles": - this._removeHandles(); - this._setupHandles(); - break; - default: - break; - } - }, - - _setupHandles: function() { - var o = this.options, handle, i, n, hname, axis, that = this; - this.handles = o.handles || - ( !$( ".ui-resizable-handle", this.element ).length ? - "e,s,se" : { - n: ".ui-resizable-n", - e: ".ui-resizable-e", - s: ".ui-resizable-s", - w: ".ui-resizable-w", - se: ".ui-resizable-se", - sw: ".ui-resizable-sw", - ne: ".ui-resizable-ne", - nw: ".ui-resizable-nw" - } ); - - this._handles = $(); - if ( this.handles.constructor === String ) { - - if ( this.handles === "all" ) { - this.handles = "n,e,s,w,se,sw,ne,nw"; - } - - n = this.handles.split( "," ); - this.handles = {}; - - for ( i = 0; i < n.length; i++ ) { - - handle = $.trim( n[ i ] ); - hname = "ui-resizable-" + handle; - axis = $( "<div>" ); - this._addClass( axis, "ui-resizable-handle " + hname ); - - axis.css( { zIndex: o.zIndex } ); - - this.handles[ handle ] = ".ui-resizable-" + handle; - this.element.append( axis ); - } - - } - - this._renderAxis = function( target ) { - - var i, axis, padPos, padWrapper; - - target = target || this.element; - - for ( i in this.handles ) { - - if ( this.handles[ i ].constructor === String ) { - this.handles[ i ] = this.element.children( this.handles[ i ] ).first().show(); - } else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) { - this.handles[ i ] = $( this.handles[ i ] ); - this._on( this.handles[ i ], { "mousedown": that._mouseDown } ); - } - - if ( this.elementIsWrapper && - this.originalElement[ 0 ] - .nodeName - .match( /^(textarea|input|select|button)$/i ) ) { - axis = $( this.handles[ i ], this.element ); - - padWrapper = /sw|ne|nw|se|n|s/.test( i ) ? - axis.outerHeight() : - axis.outerWidth(); - - padPos = [ "padding", - /ne|nw|n/.test( i ) ? "Top" : - /se|sw|s/.test( i ) ? "Bottom" : - /^e$/.test( i ) ? "Right" : "Left" ].join( "" ); - - target.css( padPos, padWrapper ); - - this._proportionallyResize(); - } - - this._handles = this._handles.add( this.handles[ i ] ); - } - }; - - // TODO: make renderAxis a prototype function - this._renderAxis( this.element ); - - this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) ); - this._handles.disableSelection(); - - this._handles.on( "mouseover", function() { - if ( !that.resizing ) { - if ( this.className ) { - axis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i ); - } - that.axis = axis && axis[ 1 ] ? axis[ 1 ] : "se"; - } - } ); - - if ( o.autoHide ) { - this._handles.hide(); - this._addClass( "ui-resizable-autohide" ); - } - }, - - _removeHandles: function() { - this._handles.remove(); - }, - - _mouseCapture: function( event ) { - var i, handle, - capture = false; + _mouseCapture: function( event ) { + var i, handle, + capture = false; - for ( i in this.handles ) { - handle = $( this.handles[ i ] )[ 0 ]; - if ( handle === event.target || $.contains( handle, event.target ) ) { - capture = true; - } - } + for ( i in this.handles ) { + handle = $( this.handles[ i ] )[ 0 ]; + if ( handle === event.target || $.contains( handle, event.target ) ) { + capture = true; + } + } - return !this.options.disabled && capture; - }, + return !this.options.disabled && capture; + }, - _mouseStart: function( event ) { + _mouseStart: function( event ) { - var curleft, curtop, cursor, - o = this.options, - el = this.element; + var curleft, curtop, cursor, + o = this.options, + el = this.element; - this.resizing = true; + this.resizing = true; - this._renderProxy(); + this._renderProxy(); - curleft = this._num( this.helper.css( "left" ) ); - curtop = this._num( this.helper.css( "top" ) ); + curleft = this._num( this.helper.css( "left" ) ); + curtop = this._num( this.helper.css( "top" ) ); - if ( o.containment ) { - curleft += $( o.containment ).scrollLeft() || 0; - curtop += $( o.containment ).scrollTop() || 0; - } + if ( o.containment ) { + curleft += $( o.containment ).scrollLeft() || 0; + curtop += $( o.containment ).scrollTop() || 0; + } - this.offset = this.helper.offset(); - this.position = { left: curleft, top: curtop }; + this.offset = this.helper.offset(); + this.position = { left: curleft, top: curtop }; - this.size = this._helper ? { - width: this.helper.width(), - height: this.helper.height() - } : { - width: el.width(), - height: el.height() - }; + this.size = this._helper ? { + width: this.helper.width(), + height: this.helper.height() + } : { + width: el.width(), + height: el.height() + }; - this.originalSize = this._helper ? { - width: el.outerWidth(), - height: el.outerHeight() - } : { - width: el.width(), - height: el.height() - }; + this.originalSize = this._helper ? { + width: el.outerWidth(), + height: el.outerHeight() + } : { + width: el.width(), + height: el.height() + }; - this.sizeDiff = { - width: el.outerWidth() - el.width(), - height: el.outerHeight() - el.height() - }; + this.sizeDiff = { + width: el.outerWidth() - el.width(), + height: el.outerHeight() - el.height() + }; - this.originalPosition = { left: curleft, top: curtop }; - this.originalMousePosition = { left: event.pageX, top: event.pageY }; + this.originalPosition = { left: curleft, top: curtop }; + this.originalMousePosition = { left: event.pageX, top: event.pageY }; - this.aspectRatio = ( typeof o.aspectRatio === "number" ) ? - o.aspectRatio : - ( ( this.originalSize.width / this.originalSize.height ) || 1 ); + this.aspectRatio = ( typeof o.aspectRatio === "number" ) ? + o.aspectRatio : + ( ( this.originalSize.width / this.originalSize.height ) || 1 ); - cursor = $( ".ui-resizable-" + this.axis ).css( "cursor" ); - $( "body" ).css( "cursor", cursor === "auto" ? this.axis + "-resize" : cursor ); + cursor = $( ".ui-resizable-" + this.axis ).css( "cursor" ); + $( "body" ).css( "cursor", cursor === "auto" ? this.axis + "-resize" : cursor ); - this._addClass( "ui-resizable-resizing" ); - this._propagate( "start", event ); - return true; - }, + this._addClass( "ui-resizable-resizing" ); + this._propagate( "start", event ); + return true; + }, - _mouseDrag: function( event ) { + _mouseDrag: function( event ) { - var data, props, - smp = this.originalMousePosition, - a = this.axis, - dx = ( event.pageX - smp.left ) || 0, - dy = ( event.pageY - smp.top ) || 0, - trigger = this._change[ a ]; + var data, props, + smp = this.originalMousePosition, + a = this.axis, + dx = ( event.pageX - smp.left ) || 0, + dy = ( event.pageY - smp.top ) || 0, + trigger = this._change[ a ]; - this._updatePrevProperties(); - - if ( !trigger ) { - return false; - } - - data = trigger.apply( this, [ event, dx, dy ] ); - - this._updateVirtualBoundaries( event.shiftKey ); - if ( this._aspectRatio || event.shiftKey ) { - data = this._updateRatio( data, event ); - } + this._updatePrevProperties(); + + if ( !trigger ) { + return false; + } + + data = trigger.apply( this, [ event, dx, dy ] ); + + this._updateVirtualBoundaries( event.shiftKey ); + if ( this._aspectRatio || event.shiftKey ) { + data = this._updateRatio( data, event ); + } - data = this._respectSize( data, event ); + data = this._respectSize( data, event ); - this._updateCache( data ); + this._updateCache( data ); - this._propagate( "resize", event ); + this._propagate( "resize", event ); - props = this._applyChanges(); + props = this._applyChanges(); - if ( !this._helper && this._proportionallyResizeElements.length ) { - this._proportionallyResize(); - } + if ( !this._helper && this._proportionallyResizeElements.length ) { + this._proportionallyResize(); + } - if ( !$.isEmptyObject( props ) ) { - this._updatePrevProperties(); - this._trigger( "resize", event, this.ui() ); - this._applyChanges(); - } + if ( !$.isEmptyObject( props ) ) { + this._updatePrevProperties(); + this._trigger( "resize", event, this.ui() ); + this._applyChanges(); + } - return false; - }, + return false; + }, - _mouseStop: function( event ) { - - this.resizing = false; - var pr, ista, soffseth, soffsetw, s, left, top, - o = this.options, that = this; - - if ( this._helper ) { - - pr = this._proportionallyResizeElements; - ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ); - soffseth = ista && this._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height; - soffsetw = ista ? 0 : that.sizeDiff.width; - - s = { - width: ( that.helper.width() - soffsetw ), - height: ( that.helper.height() - soffseth ) - }; - left = ( parseFloat( that.element.css( "left" ) ) + - ( that.position.left - that.originalPosition.left ) ) || null; - top = ( parseFloat( that.element.css( "top" ) ) + - ( that.position.top - that.originalPosition.top ) ) || null; - - if ( !o.animate ) { - this.element.css( $.extend( s, { top: top, left: left } ) ); - } - - that.helper.height( that.size.height ); - that.helper.width( that.size.width ); - - if ( this._helper && !o.animate ) { - this._proportionallyResize(); - } - } - - $( "body" ).css( "cursor", "auto" ); - - this._removeClass( "ui-resizable-resizing" ); - - this._propagate( "stop", event ); - - if ( this._helper ) { - this.helper.remove(); - } - - return false; - - }, - - _updatePrevProperties: function() { - this.prevPosition = { - top: this.position.top, - left: this.position.left - }; - this.prevSize = { - width: this.size.width, - height: this.size.height - }; - }, - - _applyChanges: function() { - var props = {}; - - if ( this.position.top !== this.prevPosition.top ) { - props.top = this.position.top + "px"; - } - if ( this.position.left !== this.prevPosition.left ) { - props.left = this.position.left + "px"; - } - if ( this.size.width !== this.prevSize.width ) { - props.width = this.size.width + "px"; - } - if ( this.size.height !== this.prevSize.height ) { - props.height = this.size.height + "px"; - } - - this.helper.css( props ); - - return props; - }, - - _updateVirtualBoundaries: function( forceAspectRatio ) { - var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, - o = this.options; - - b = { - minWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0, - maxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity, - minHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0, - maxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity - }; - - if ( this._aspectRatio || forceAspectRatio ) { - pMinWidth = b.minHeight * this.aspectRatio; - pMinHeight = b.minWidth / this.aspectRatio; - pMaxWidth = b.maxHeight * this.aspectRatio; - pMaxHeight = b.maxWidth / this.aspectRatio; - - if ( pMinWidth > b.minWidth ) { - b.minWidth = pMinWidth; - } - if ( pMinHeight > b.minHeight ) { - b.minHeight = pMinHeight; - } - if ( pMaxWidth < b.maxWidth ) { - b.maxWidth = pMaxWidth; - } - if ( pMaxHeight < b.maxHeight ) { - b.maxHeight = pMaxHeight; - } - } - this._vBoundaries = b; - }, - - _updateCache: function( data ) { - this.offset = this.helper.offset(); - if ( this._isNumber( data.left ) ) { - this.position.left = data.left; - } - if ( this._isNumber( data.top ) ) { - this.position.top = data.top; - } - if ( this._isNumber( data.height ) ) { - this.size.height = data.height; - } - if ( this._isNumber( data.width ) ) { - this.size.width = data.width; - } - }, - - _updateRatio: function( data ) { - - var cpos = this.position, - csize = this.size, - a = this.axis; - - if ( this._isNumber( data.height ) ) { - data.width = ( data.height * this.aspectRatio ); - } else if ( this._isNumber( data.width ) ) { - data.height = ( data.width / this.aspectRatio ); - } - - if ( a === "sw" ) { - data.left = cpos.left + ( csize.width - data.width ); - data.top = null; - } - if ( a === "nw" ) { - data.top = cpos.top + ( csize.height - data.height ); - data.left = cpos.left + ( csize.width - data.width ); - } - - return data; - }, - - _respectSize: function( data ) { - - var o = this._vBoundaries, - a = this.axis, - ismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ), - ismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ), - isminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ), - isminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ), - dw = this.originalPosition.left + this.originalSize.width, - dh = this.originalPosition.top + this.originalSize.height, - cw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a ); - if ( isminw ) { - data.width = o.minWidth; - } - if ( isminh ) { - data.height = o.minHeight; - } - if ( ismaxw ) { - data.width = o.maxWidth; - } - if ( ismaxh ) { - data.height = o.maxHeight; - } - - if ( isminw && cw ) { - data.left = dw - o.minWidth; - } - if ( ismaxw && cw ) { - data.left = dw - o.maxWidth; - } - if ( isminh && ch ) { - data.top = dh - o.minHeight; - } - if ( ismaxh && ch ) { - data.top = dh - o.maxHeight; - } - - // Fixing jump error on top/left - bug #2330 - if ( !data.width && !data.height && !data.left && data.top ) { - data.top = null; - } else if ( !data.width && !data.height && !data.top && data.left ) { - data.left = null; - } - - return data; - }, - - _getPaddingPlusBorderDimensions: function( element ) { - var i = 0, - widths = [], - borders = [ - element.css( "borderTopWidth" ), - element.css( "borderRightWidth" ), - element.css( "borderBottomWidth" ), - element.css( "borderLeftWidth" ) - ], - paddings = [ - element.css( "paddingTop" ), - element.css( "paddingRight" ), - element.css( "paddingBottom" ), - element.css( "paddingLeft" ) - ]; - - for ( ; i < 4; i++ ) { - widths[ i ] = ( parseFloat( borders[ i ] ) || 0 ); - widths[ i ] += ( parseFloat( paddings[ i ] ) || 0 ); - } - - return { - height: widths[ 0 ] + widths[ 2 ], - width: widths[ 1 ] + widths[ 3 ] - }; - }, - - _proportionallyResize: function() { - - if ( !this._proportionallyResizeElements.length ) { - return; - } - - var prel, - i = 0, - element = this.helper || this.element; - - for ( ; i < this._proportionallyResizeElements.length; i++ ) { - - prel = this._proportionallyResizeElements[ i ]; - - // TODO: Seems like a bug to cache this.outerDimensions - // considering that we are in a loop. - if ( !this.outerDimensions ) { - this.outerDimensions = this._getPaddingPlusBorderDimensions( prel ); - } - - prel.css( { - height: ( element.height() - this.outerDimensions.height ) || 0, - width: ( element.width() - this.outerDimensions.width ) || 0 - } ); - - } - - }, - - _renderProxy: function() { - - var el = this.element, o = this.options; - this.elementOffset = el.offset(); - - if ( this._helper ) { - - this.helper = this.helper || $( "<div style='overflow:hidden;'></div>" ); - - this._addClass( this.helper, this._helper ); - this.helper.css( { - width: this.element.outerWidth(), - height: this.element.outerHeight(), - position: "absolute", - left: this.elementOffset.left + "px", - top: this.elementOffset.top + "px", - zIndex: ++o.zIndex //TODO: Don't modify option - } ); - - this.helper - .appendTo( "body" ) - .disableSelection(); - - } else { - this.helper = this.element; - } - - }, - - _change: { - e: function( event, dx ) { - return { width: this.originalSize.width + dx }; - }, - w: function( event, dx ) { - var cs = this.originalSize, sp = this.originalPosition; - return { left: sp.left + dx, width: cs.width - dx }; - }, - n: function( event, dx, dy ) { - var cs = this.originalSize, sp = this.originalPosition; - return { top: sp.top + dy, height: cs.height - dy }; - }, - s: function( event, dx, dy ) { - return { height: this.originalSize.height + dy }; - }, - se: function( event, dx, dy ) { - return $.extend( this._change.s.apply( this, arguments ), - this._change.e.apply( this, [ event, dx, dy ] ) ); - }, - sw: function( event, dx, dy ) { - return $.extend( this._change.s.apply( this, arguments ), - this._change.w.apply( this, [ event, dx, dy ] ) ); - }, - ne: function( event, dx, dy ) { - return $.extend( this._change.n.apply( this, arguments ), - this._change.e.apply( this, [ event, dx, dy ] ) ); - }, - nw: function( event, dx, dy ) { - return $.extend( this._change.n.apply( this, arguments ), - this._change.w.apply( this, [ event, dx, dy ] ) ); - } - }, - - _propagate: function( n, event ) { - $.ui.plugin.call( this, n, [ event, this.ui() ] ); - ( n !== "resize" && this._trigger( n, event, this.ui() ) ); - }, - - plugins: {}, - - ui: function() { - return { - originalElement: this.originalElement, - element: this.element, - helper: this.helper, - position: this.position, - size: this.size, - originalSize: this.originalSize, - originalPosition: this.originalPosition - }; - } - -} ); - -/* + _mouseStop: function( event ) { + + this.resizing = false; + var pr, ista, soffseth, soffsetw, s, left, top, + o = this.options, that = this; + + if ( this._helper ) { + + pr = this._proportionallyResizeElements; + ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ); + soffseth = ista && this._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height; + soffsetw = ista ? 0 : that.sizeDiff.width; + + s = { + width: ( that.helper.width() - soffsetw ), + height: ( that.helper.height() - soffseth ) + }; + left = ( parseFloat( that.element.css( "left" ) ) + + ( that.position.left - that.originalPosition.left ) ) || null; + top = ( parseFloat( that.element.css( "top" ) ) + + ( that.position.top - that.originalPosition.top ) ) || null; + + if ( !o.animate ) { + this.element.css( $.extend( s, { top: top, left: left } ) ); + } + + that.helper.height( that.size.height ); + that.helper.width( that.size.width ); + + if ( this._helper && !o.animate ) { + this._proportionallyResize(); + } + } + + $( "body" ).css( "cursor", "auto" ); + + this._removeClass( "ui-resizable-resizing" ); + + this._propagate( "stop", event ); + + if ( this._helper ) { + this.helper.remove(); + } + + return false; + + }, + + _updatePrevProperties: function() { + this.prevPosition = { + top: this.position.top, + left: this.position.left + }; + this.prevSize = { + width: this.size.width, + height: this.size.height + }; + }, + + _applyChanges: function() { + var props = {}; + + if ( this.position.top !== this.prevPosition.top ) { + props.top = this.position.top + "px"; + } + if ( this.position.left !== this.prevPosition.left ) { + props.left = this.position.left + "px"; + } + if ( this.size.width !== this.prevSize.width ) { + props.width = this.size.width + "px"; + } + if ( this.size.height !== this.prevSize.height ) { + props.height = this.size.height + "px"; + } + + this.helper.css( props ); + + return props; + }, + + _updateVirtualBoundaries: function( forceAspectRatio ) { + var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, + o = this.options; + + b = { + minWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0, + maxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity, + minHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0, + maxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity + }; + + if ( this._aspectRatio || forceAspectRatio ) { + pMinWidth = b.minHeight * this.aspectRatio; + pMinHeight = b.minWidth / this.aspectRatio; + pMaxWidth = b.maxHeight * this.aspectRatio; + pMaxHeight = b.maxWidth / this.aspectRatio; + + if ( pMinWidth > b.minWidth ) { + b.minWidth = pMinWidth; + } + if ( pMinHeight > b.minHeight ) { + b.minHeight = pMinHeight; + } + if ( pMaxWidth < b.maxWidth ) { + b.maxWidth = pMaxWidth; + } + if ( pMaxHeight < b.maxHeight ) { + b.maxHeight = pMaxHeight; + } + } + this._vBoundaries = b; + }, + + _updateCache: function( data ) { + this.offset = this.helper.offset(); + if ( this._isNumber( data.left ) ) { + this.position.left = data.left; + } + if ( this._isNumber( data.top ) ) { + this.position.top = data.top; + } + if ( this._isNumber( data.height ) ) { + this.size.height = data.height; + } + if ( this._isNumber( data.width ) ) { + this.size.width = data.width; + } + }, + + _updateRatio: function( data ) { + + var cpos = this.position, + csize = this.size, + a = this.axis; + + if ( this._isNumber( data.height ) ) { + data.width = ( data.height * this.aspectRatio ); + } else if ( this._isNumber( data.width ) ) { + data.height = ( data.width / this.aspectRatio ); + } + + if ( a === "sw" ) { + data.left = cpos.left + ( csize.width - data.width ); + data.top = null; + } + if ( a === "nw" ) { + data.top = cpos.top + ( csize.height - data.height ); + data.left = cpos.left + ( csize.width - data.width ); + } + + return data; + }, + + _respectSize: function( data ) { + + var o = this._vBoundaries, + a = this.axis, + ismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ), + ismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ), + isminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ), + isminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ), + dw = this.originalPosition.left + this.originalSize.width, + dh = this.originalPosition.top + this.originalSize.height, + cw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a ); + if ( isminw ) { + data.width = o.minWidth; + } + if ( isminh ) { + data.height = o.minHeight; + } + if ( ismaxw ) { + data.width = o.maxWidth; + } + if ( ismaxh ) { + data.height = o.maxHeight; + } + + if ( isminw && cw ) { + data.left = dw - o.minWidth; + } + if ( ismaxw && cw ) { + data.left = dw - o.maxWidth; + } + if ( isminh && ch ) { + data.top = dh - o.minHeight; + } + if ( ismaxh && ch ) { + data.top = dh - o.maxHeight; + } + + // Fixing jump error on top/left - bug #2330 + if ( !data.width && !data.height && !data.left && data.top ) { + data.top = null; + } else if ( !data.width && !data.height && !data.top && data.left ) { + data.left = null; + } + + return data; + }, + + _getPaddingPlusBorderDimensions: function( element ) { + var i = 0, + widths = [], + borders = [ + element.css( "borderTopWidth" ), + element.css( "borderRightWidth" ), + element.css( "borderBottomWidth" ), + element.css( "borderLeftWidth" ) + ], + paddings = [ + element.css( "paddingTop" ), + element.css( "paddingRight" ), + element.css( "paddingBottom" ), + element.css( "paddingLeft" ) + ]; + + for ( ; i < 4; i++ ) { + widths[ i ] = ( parseFloat( borders[ i ] ) || 0 ); + widths[ i ] += ( parseFloat( paddings[ i ] ) || 0 ); + } + + return { + height: widths[ 0 ] + widths[ 2 ], + width: widths[ 1 ] + widths[ 3 ] + }; + }, + + _proportionallyResize: function() { + + if ( !this._proportionallyResizeElements.length ) { + return; + } + + var prel, + i = 0, + element = this.helper || this.element; + + for ( ; i < this._proportionallyResizeElements.length; i++ ) { + + prel = this._proportionallyResizeElements[ i ]; + + // TODO: Seems like a bug to cache this.outerDimensions + // considering that we are in a loop. + if ( !this.outerDimensions ) { + this.outerDimensions = this._getPaddingPlusBorderDimensions( prel ); + } + + prel.css( { + height: ( element.height() - this.outerDimensions.height ) || 0, + width: ( element.width() - this.outerDimensions.width ) || 0 + } ); + + } + + }, + + _renderProxy: function() { + + var el = this.element, o = this.options; + this.elementOffset = el.offset(); + + if ( this._helper ) { + + this.helper = this.helper || $( "<div></div>" ).css( { overflow: "hidden" } ); + + this._addClass( this.helper, this._helper ); + this.helper.css( { + width: this.element.outerWidth(), + height: this.element.outerHeight(), + position: "absolute", + left: this.elementOffset.left + "px", + top: this.elementOffset.top + "px", + zIndex: ++o.zIndex //TODO: Don't modify option + } ); + + this.helper + .appendTo( "body" ) + .disableSelection(); + + } else { + this.helper = this.element; + } + + }, + + _change: { + e: function( event, dx ) { + return { width: this.originalSize.width + dx }; + }, + w: function( event, dx ) { + var cs = this.originalSize, sp = this.originalPosition; + return { left: sp.left + dx, width: cs.width - dx }; + }, + n: function( event, dx, dy ) { + var cs = this.originalSize, sp = this.originalPosition; + return { top: sp.top + dy, height: cs.height - dy }; + }, + s: function( event, dx, dy ) { + return { height: this.originalSize.height + dy }; + }, + se: function( event, dx, dy ) { + return $.extend( this._change.s.apply( this, arguments ), + this._change.e.apply( this, [ event, dx, dy ] ) ); + }, + sw: function( event, dx, dy ) { + return $.extend( this._change.s.apply( this, arguments ), + this._change.w.apply( this, [ event, dx, dy ] ) ); + }, + ne: function( event, dx, dy ) { + return $.extend( this._change.n.apply( this, arguments ), + this._change.e.apply( this, [ event, dx, dy ] ) ); + }, + nw: function( event, dx, dy ) { + return $.extend( this._change.n.apply( this, arguments ), + this._change.w.apply( this, [ event, dx, dy ] ) ); + } + }, + + _propagate: function( n, event ) { + $.ui.plugin.call( this, n, [ event, this.ui() ] ); + if ( n !== "resize" ) { + this._trigger( n, event, this.ui() ); + } + }, + + plugins: {}, + + ui: function() { + return { + originalElement: this.originalElement, + element: this.element, + helper: this.helper, + position: this.position, + size: this.size, + originalSize: this.originalSize, + originalPosition: this.originalPosition + }; + } + + } ); + + /* * Resizable Extensions */ -$.ui.plugin.add( "resizable", "animate", { - - stop: function( event ) { - var that = $( this ).resizable( "instance" ), - o = that.options, - pr = that._proportionallyResizeElements, - ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ), - soffseth = ista && that._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height, - soffsetw = ista ? 0 : that.sizeDiff.width, - style = { - width: ( that.size.width - soffsetw ), - height: ( that.size.height - soffseth ) - }, - left = ( parseFloat( that.element.css( "left" ) ) + - ( that.position.left - that.originalPosition.left ) ) || null, - top = ( parseFloat( that.element.css( "top" ) ) + - ( that.position.top - that.originalPosition.top ) ) || null; - - that.element.animate( - $.extend( style, top && left ? { top: top, left: left } : {} ), { - duration: o.animateDuration, - easing: o.animateEasing, - step: function() { - - var data = { - width: parseFloat( that.element.css( "width" ) ), - height: parseFloat( that.element.css( "height" ) ), - top: parseFloat( that.element.css( "top" ) ), - left: parseFloat( that.element.css( "left" ) ) - }; - - if ( pr && pr.length ) { - $( pr[ 0 ] ).css( { width: data.width, height: data.height } ); - } - - // Propagating resize, and updating values for each animation step - that._updateCache( data ); - that._propagate( "resize", event ); - - } - } - ); - } - -} ); - -$.ui.plugin.add( "resizable", "containment", { - - start: function() { - var element, p, co, ch, cw, width, height, - that = $( this ).resizable( "instance" ), - o = that.options, - el = that.element, - oc = o.containment, - ce = ( oc instanceof $ ) ? - oc.get( 0 ) : - ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc; - - if ( !ce ) { - return; - } - - that.containerElement = $( ce ); - - if ( /document/.test( oc ) || oc === document ) { - that.containerOffset = { - left: 0, - top: 0 - }; - that.containerPosition = { - left: 0, - top: 0 - }; - - that.parentData = { - element: $( document ), - left: 0, - top: 0, - width: $( document ).width(), - height: $( document ).height() || document.body.parentNode.scrollHeight - }; - } else { - element = $( ce ); - p = []; - $( [ "Top", "Right", "Left", "Bottom" ] ).each( function( i, name ) { - p[ i ] = that._num( element.css( "padding" + name ) ); - } ); - - that.containerOffset = element.offset(); - that.containerPosition = element.position(); - that.containerSize = { - height: ( element.innerHeight() - p[ 3 ] ), - width: ( element.innerWidth() - p[ 1 ] ) - }; - - co = that.containerOffset; - ch = that.containerSize.height; - cw = that.containerSize.width; - width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw ); - height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ; - - that.parentData = { - element: ce, - left: co.left, - top: co.top, - width: width, - height: height - }; - } - }, - - resize: function( event ) { - var woset, hoset, isParent, isOffsetRelative, - that = $( this ).resizable( "instance" ), - o = that.options, - co = that.containerOffset, - cp = that.position, - pRatio = that._aspectRatio || event.shiftKey, - cop = { - top: 0, - left: 0 - }, - ce = that.containerElement, - continueResize = true; - - if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) { - cop = co; - } - - if ( cp.left < ( that._helper ? co.left : 0 ) ) { - that.size.width = that.size.width + - ( that._helper ? - ( that.position.left - co.left ) : - ( that.position.left - cop.left ) ); - - if ( pRatio ) { - that.size.height = that.size.width / that.aspectRatio; - continueResize = false; - } - that.position.left = o.helper ? co.left : 0; - } - - if ( cp.top < ( that._helper ? co.top : 0 ) ) { - that.size.height = that.size.height + - ( that._helper ? - ( that.position.top - co.top ) : - that.position.top ); - - if ( pRatio ) { - that.size.width = that.size.height * that.aspectRatio; - continueResize = false; - } - that.position.top = that._helper ? co.top : 0; - } - - isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 ); - isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) ); - - if ( isParent && isOffsetRelative ) { - that.offset.left = that.parentData.left + that.position.left; - that.offset.top = that.parentData.top + that.position.top; - } else { - that.offset.left = that.element.offset().left; - that.offset.top = that.element.offset().top; - } - - woset = Math.abs( that.sizeDiff.width + - ( that._helper ? - that.offset.left - cop.left : - ( that.offset.left - co.left ) ) ); - - hoset = Math.abs( that.sizeDiff.height + - ( that._helper ? - that.offset.top - cop.top : - ( that.offset.top - co.top ) ) ); - - if ( woset + that.size.width >= that.parentData.width ) { - that.size.width = that.parentData.width - woset; - if ( pRatio ) { - that.size.height = that.size.width / that.aspectRatio; - continueResize = false; - } - } - - if ( hoset + that.size.height >= that.parentData.height ) { - that.size.height = that.parentData.height - hoset; - if ( pRatio ) { - that.size.width = that.size.height * that.aspectRatio; - continueResize = false; - } - } - - if ( !continueResize ) { - that.position.left = that.prevPosition.left; - that.position.top = that.prevPosition.top; - that.size.width = that.prevSize.width; - that.size.height = that.prevSize.height; - } - }, - - stop: function() { - var that = $( this ).resizable( "instance" ), - o = that.options, - co = that.containerOffset, - cop = that.containerPosition, - ce = that.containerElement, - helper = $( that.helper ), - ho = helper.offset(), - w = helper.outerWidth() - that.sizeDiff.width, - h = helper.outerHeight() - that.sizeDiff.height; - - if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) { - $( this ).css( { - left: ho.left - cop.left - co.left, - width: w, - height: h - } ); - } - - if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) { - $( this ).css( { - left: ho.left - cop.left - co.left, - width: w, - height: h - } ); - } - } -} ); - -$.ui.plugin.add( "resizable", "alsoResize", { - - start: function() { - var that = $( this ).resizable( "instance" ), - o = that.options; - - $( o.alsoResize ).each( function() { - var el = $( this ); - el.data( "ui-resizable-alsoresize", { - width: parseFloat( el.width() ), height: parseFloat( el.height() ), - left: parseFloat( el.css( "left" ) ), top: parseFloat( el.css( "top" ) ) - } ); - } ); - }, - - resize: function( event, ui ) { - var that = $( this ).resizable( "instance" ), - o = that.options, - os = that.originalSize, - op = that.originalPosition, - delta = { - height: ( that.size.height - os.height ) || 0, - width: ( that.size.width - os.width ) || 0, - top: ( that.position.top - op.top ) || 0, - left: ( that.position.left - op.left ) || 0 - }; - - $( o.alsoResize ).each( function() { - var el = $( this ), start = $( this ).data( "ui-resizable-alsoresize" ), style = {}, - css = el.parents( ui.originalElement[ 0 ] ).length ? - [ "width", "height" ] : - [ "width", "height", "top", "left" ]; - - $.each( css, function( i, prop ) { - var sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 ); - if ( sum && sum >= 0 ) { - style[ prop ] = sum || null; - } - } ); - - el.css( style ); - } ); - }, - - stop: function() { - $( this ).removeData( "ui-resizable-alsoresize" ); - } -} ); - -$.ui.plugin.add( "resizable", "ghost", { - - start: function() { - - var that = $( this ).resizable( "instance" ), cs = that.size; - - that.ghost = that.originalElement.clone(); - that.ghost.css( { - opacity: 0.25, - display: "block", - position: "relative", - height: cs.height, - width: cs.width, - margin: 0, - left: 0, - top: 0 - } ); - - that._addClass( that.ghost, "ui-resizable-ghost" ); - - // DEPRECATED - // TODO: remove after 1.12 - if ( $.uiBackCompat !== false && typeof that.options.ghost === "string" ) { - - // Ghost option - that.ghost.addClass( this.options.ghost ); - } - - that.ghost.appendTo( that.helper ); - - }, - - resize: function() { - var that = $( this ).resizable( "instance" ); - if ( that.ghost ) { - that.ghost.css( { - position: "relative", - height: that.size.height, - width: that.size.width - } ); - } - }, - - stop: function() { - var that = $( this ).resizable( "instance" ); - if ( that.ghost && that.helper ) { - that.helper.get( 0 ).removeChild( that.ghost.get( 0 ) ); - } - } - -} ); - -$.ui.plugin.add( "resizable", "grid", { - - resize: function() { - var outerDimensions, - that = $( this ).resizable( "instance" ), - o = that.options, - cs = that.size, - os = that.originalSize, - op = that.originalPosition, - a = that.axis, - grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid, - gridX = ( grid[ 0 ] || 1 ), - gridY = ( grid[ 1 ] || 1 ), - ox = Math.round( ( cs.width - os.width ) / gridX ) * gridX, - oy = Math.round( ( cs.height - os.height ) / gridY ) * gridY, - newWidth = os.width + ox, - newHeight = os.height + oy, - isMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ), - isMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ), - isMinWidth = o.minWidth && ( o.minWidth > newWidth ), - isMinHeight = o.minHeight && ( o.minHeight > newHeight ); - - o.grid = grid; - - if ( isMinWidth ) { - newWidth += gridX; - } - if ( isMinHeight ) { - newHeight += gridY; - } - if ( isMaxWidth ) { - newWidth -= gridX; - } - if ( isMaxHeight ) { - newHeight -= gridY; - } - - if ( /^(se|s|e)$/.test( a ) ) { - that.size.width = newWidth; - that.size.height = newHeight; - } else if ( /^(ne)$/.test( a ) ) { - that.size.width = newWidth; - that.size.height = newHeight; - that.position.top = op.top - oy; - } else if ( /^(sw)$/.test( a ) ) { - that.size.width = newWidth; - that.size.height = newHeight; - that.position.left = op.left - ox; - } else { - if ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) { - outerDimensions = that._getPaddingPlusBorderDimensions( this ); - } - - if ( newHeight - gridY > 0 ) { - that.size.height = newHeight; - that.position.top = op.top - oy; - } else { - newHeight = gridY - outerDimensions.height; - that.size.height = newHeight; - that.position.top = op.top + os.height - newHeight; - } - if ( newWidth - gridX > 0 ) { - that.size.width = newWidth; - that.position.left = op.left - ox; - } else { - newWidth = gridX - outerDimensions.width; - that.size.width = newWidth; - that.position.left = op.left + os.width - newWidth; - } - } - } - -} ); - -var widgetsResizable = $.ui.resizable; - - -/*! - * jQuery UI Selectable 1.12.1 + $.ui.plugin.add( "resizable", "animate", { + + stop: function( event ) { + var that = $( this ).resizable( "instance" ), + o = that.options, + pr = that._proportionallyResizeElements, + ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ), + soffseth = ista && that._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height, + soffsetw = ista ? 0 : that.sizeDiff.width, + style = { + width: ( that.size.width - soffsetw ), + height: ( that.size.height - soffseth ) + }, + left = ( parseFloat( that.element.css( "left" ) ) + + ( that.position.left - that.originalPosition.left ) ) || null, + top = ( parseFloat( that.element.css( "top" ) ) + + ( that.position.top - that.originalPosition.top ) ) || null; + + that.element.animate( + $.extend( style, top && left ? { top: top, left: left } : {} ), { + duration: o.animateDuration, + easing: o.animateEasing, + step: function() { + + var data = { + width: parseFloat( that.element.css( "width" ) ), + height: parseFloat( that.element.css( "height" ) ), + top: parseFloat( that.element.css( "top" ) ), + left: parseFloat( that.element.css( "left" ) ) + }; + + if ( pr && pr.length ) { + $( pr[ 0 ] ).css( { width: data.width, height: data.height } ); + } + + // Propagating resize, and updating values for each animation step + that._updateCache( data ); + that._propagate( "resize", event ); + + } + } + ); + } + + } ); + + $.ui.plugin.add( "resizable", "containment", { + + start: function() { + var element, p, co, ch, cw, width, height, + that = $( this ).resizable( "instance" ), + o = that.options, + el = that.element, + oc = o.containment, + ce = ( oc instanceof $ ) ? + oc.get( 0 ) : + ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc; + + if ( !ce ) { + return; + } + + that.containerElement = $( ce ); + + if ( /document/.test( oc ) || oc === document ) { + that.containerOffset = { + left: 0, + top: 0 + }; + that.containerPosition = { + left: 0, + top: 0 + }; + + that.parentData = { + element: $( document ), + left: 0, + top: 0, + width: $( document ).width(), + height: $( document ).height() || document.body.parentNode.scrollHeight + }; + } else { + element = $( ce ); + p = []; + $( [ "Top", "Right", "Left", "Bottom" ] ).each( function( i, name ) { + p[ i ] = that._num( element.css( "padding" + name ) ); + } ); + + that.containerOffset = element.offset(); + that.containerPosition = element.position(); + that.containerSize = { + height: ( element.innerHeight() - p[ 3 ] ), + width: ( element.innerWidth() - p[ 1 ] ) + }; + + co = that.containerOffset; + ch = that.containerSize.height; + cw = that.containerSize.width; + width = ( that._hasScroll( ce, "left" ) ? ce.scrollWidth : cw ); + height = ( that._hasScroll( ce ) ? ce.scrollHeight : ch ); + + that.parentData = { + element: ce, + left: co.left, + top: co.top, + width: width, + height: height + }; + } + }, + + resize: function( event ) { + var woset, hoset, isParent, isOffsetRelative, + that = $( this ).resizable( "instance" ), + o = that.options, + co = that.containerOffset, + cp = that.position, + pRatio = that._aspectRatio || event.shiftKey, + cop = { + top: 0, + left: 0 + }, + ce = that.containerElement, + continueResize = true; + + if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) { + cop = co; + } + + if ( cp.left < ( that._helper ? co.left : 0 ) ) { + that.size.width = that.size.width + + ( that._helper ? + ( that.position.left - co.left ) : + ( that.position.left - cop.left ) ); + + if ( pRatio ) { + that.size.height = that.size.width / that.aspectRatio; + continueResize = false; + } + that.position.left = o.helper ? co.left : 0; + } + + if ( cp.top < ( that._helper ? co.top : 0 ) ) { + that.size.height = that.size.height + + ( that._helper ? + ( that.position.top - co.top ) : + that.position.top ); + + if ( pRatio ) { + that.size.width = that.size.height * that.aspectRatio; + continueResize = false; + } + that.position.top = that._helper ? co.top : 0; + } + + isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 ); + isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) ); + + if ( isParent && isOffsetRelative ) { + that.offset.left = that.parentData.left + that.position.left; + that.offset.top = that.parentData.top + that.position.top; + } else { + that.offset.left = that.element.offset().left; + that.offset.top = that.element.offset().top; + } + + woset = Math.abs( that.sizeDiff.width + + ( that._helper ? + that.offset.left - cop.left : + ( that.offset.left - co.left ) ) ); + + hoset = Math.abs( that.sizeDiff.height + + ( that._helper ? + that.offset.top - cop.top : + ( that.offset.top - co.top ) ) ); + + if ( woset + that.size.width >= that.parentData.width ) { + that.size.width = that.parentData.width - woset; + if ( pRatio ) { + that.size.height = that.size.width / that.aspectRatio; + continueResize = false; + } + } + + if ( hoset + that.size.height >= that.parentData.height ) { + that.size.height = that.parentData.height - hoset; + if ( pRatio ) { + that.size.width = that.size.height * that.aspectRatio; + continueResize = false; + } + } + + if ( !continueResize ) { + that.position.left = that.prevPosition.left; + that.position.top = that.prevPosition.top; + that.size.width = that.prevSize.width; + that.size.height = that.prevSize.height; + } + }, + + stop: function() { + var that = $( this ).resizable( "instance" ), + o = that.options, + co = that.containerOffset, + cop = that.containerPosition, + ce = that.containerElement, + helper = $( that.helper ), + ho = helper.offset(), + w = helper.outerWidth() - that.sizeDiff.width, + h = helper.outerHeight() - that.sizeDiff.height; + + if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) { + $( this ).css( { + left: ho.left - cop.left - co.left, + width: w, + height: h + } ); + } + + if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) { + $( this ).css( { + left: ho.left - cop.left - co.left, + width: w, + height: h + } ); + } + } + } ); + + $.ui.plugin.add( "resizable", "alsoResize", { + + start: function() { + var that = $( this ).resizable( "instance" ), + o = that.options; + + $( o.alsoResize ).each( function() { + var el = $( this ); + el.data( "ui-resizable-alsoresize", { + width: parseFloat( el.width() ), height: parseFloat( el.height() ), + left: parseFloat( el.css( "left" ) ), top: parseFloat( el.css( "top" ) ) + } ); + } ); + }, + + resize: function( event, ui ) { + var that = $( this ).resizable( "instance" ), + o = that.options, + os = that.originalSize, + op = that.originalPosition, + delta = { + height: ( that.size.height - os.height ) || 0, + width: ( that.size.width - os.width ) || 0, + top: ( that.position.top - op.top ) || 0, + left: ( that.position.left - op.left ) || 0 + }; + + $( o.alsoResize ).each( function() { + var el = $( this ), start = $( this ).data( "ui-resizable-alsoresize" ), style = {}, + css = el.parents( ui.originalElement[ 0 ] ).length ? + [ "width", "height" ] : + [ "width", "height", "top", "left" ]; + + $.each( css, function( i, prop ) { + var sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 ); + if ( sum && sum >= 0 ) { + style[ prop ] = sum || null; + } + } ); + + el.css( style ); + } ); + }, + + stop: function() { + $( this ).removeData( "ui-resizable-alsoresize" ); + } + } ); + + $.ui.plugin.add( "resizable", "ghost", { + + start: function() { + + var that = $( this ).resizable( "instance" ), cs = that.size; + + that.ghost = that.originalElement.clone(); + that.ghost.css( { + opacity: 0.25, + display: "block", + position: "relative", + height: cs.height, + width: cs.width, + margin: 0, + left: 0, + top: 0 + } ); + + that._addClass( that.ghost, "ui-resizable-ghost" ); + + // DEPRECATED + // TODO: remove after 1.12 + if ( $.uiBackCompat !== false && typeof that.options.ghost === "string" ) { + + // Ghost option + that.ghost.addClass( this.options.ghost ); + } + + that.ghost.appendTo( that.helper ); + + }, + + resize: function() { + var that = $( this ).resizable( "instance" ); + if ( that.ghost ) { + that.ghost.css( { + position: "relative", + height: that.size.height, + width: that.size.width + } ); + } + }, + + stop: function() { + var that = $( this ).resizable( "instance" ); + if ( that.ghost && that.helper ) { + that.helper.get( 0 ).removeChild( that.ghost.get( 0 ) ); + } + } + + } ); + + $.ui.plugin.add( "resizable", "grid", { + + resize: function() { + var outerDimensions, + that = $( this ).resizable( "instance" ), + o = that.options, + cs = that.size, + os = that.originalSize, + op = that.originalPosition, + a = that.axis, + grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid, + gridX = ( grid[ 0 ] || 1 ), + gridY = ( grid[ 1 ] || 1 ), + ox = Math.round( ( cs.width - os.width ) / gridX ) * gridX, + oy = Math.round( ( cs.height - os.height ) / gridY ) * gridY, + newWidth = os.width + ox, + newHeight = os.height + oy, + isMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ), + isMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ), + isMinWidth = o.minWidth && ( o.minWidth > newWidth ), + isMinHeight = o.minHeight && ( o.minHeight > newHeight ); + + o.grid = grid; + + if ( isMinWidth ) { + newWidth += gridX; + } + if ( isMinHeight ) { + newHeight += gridY; + } + if ( isMaxWidth ) { + newWidth -= gridX; + } + if ( isMaxHeight ) { + newHeight -= gridY; + } + + if ( /^(se|s|e)$/.test( a ) ) { + that.size.width = newWidth; + that.size.height = newHeight; + } else if ( /^(ne)$/.test( a ) ) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.top = op.top - oy; + } else if ( /^(sw)$/.test( a ) ) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.left = op.left - ox; + } else { + if ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) { + outerDimensions = that._getPaddingPlusBorderDimensions( this ); + } + + if ( newHeight - gridY > 0 ) { + that.size.height = newHeight; + that.position.top = op.top - oy; + } else { + newHeight = gridY - outerDimensions.height; + that.size.height = newHeight; + that.position.top = op.top + os.height - newHeight; + } + if ( newWidth - gridX > 0 ) { + that.size.width = newWidth; + that.position.left = op.left - ox; + } else { + newWidth = gridX - outerDimensions.width; + that.size.width = newWidth; + that.position.left = op.left + os.width - newWidth; + } + } + } + + } ); + + var widgetsResizable = $.ui.resizable; + + + /*! + * jQuery UI Selectable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -4922,286 +4956,289 @@ var widgetsResizable = $.ui.resizable; //>>css.structure: ../../themes/base/selectable.css - -var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, { - version: "1.12.1", - options: { - appendTo: "body", - autoRefresh: true, - distance: 0, - filter: "*", - tolerance: "touch", - - // Callbacks - selected: null, - selecting: null, - start: null, - stop: null, - unselected: null, - unselecting: null - }, - _create: function() { - var that = this; - - this._addClass( "ui-selectable" ); - - this.dragged = false; - - // Cache selectee children based on filter - this.refresh = function() { - that.elementPos = $( that.element[ 0 ] ).offset(); - that.selectees = $( that.options.filter, that.element[ 0 ] ); - that._addClass( that.selectees, "ui-selectee" ); - that.selectees.each( function() { - var $this = $( this ), - selecteeOffset = $this.offset(), - pos = { - left: selecteeOffset.left - that.elementPos.left, - top: selecteeOffset.top - that.elementPos.top - }; - $.data( this, "selectable-item", { - element: this, - $element: $this, - left: pos.left, - top: pos.top, - right: pos.left + $this.outerWidth(), - bottom: pos.top + $this.outerHeight(), - startselected: false, - selected: $this.hasClass( "ui-selected" ), - selecting: $this.hasClass( "ui-selecting" ), - unselecting: $this.hasClass( "ui-unselecting" ) - } ); - } ); - }; - this.refresh(); - - this._mouseInit(); - - this.helper = $( "<div>" ); - this._addClass( this.helper, "ui-selectable-helper" ); - }, - - _destroy: function() { - this.selectees.removeData( "selectable-item" ); - this._mouseDestroy(); - }, - - _mouseStart: function( event ) { - var that = this, - options = this.options; - - this.opos = [ event.pageX, event.pageY ]; - this.elementPos = $( this.element[ 0 ] ).offset(); - - if ( this.options.disabled ) { - return; - } - - this.selectees = $( options.filter, this.element[ 0 ] ); - - this._trigger( "start", event ); - - $( options.appendTo ).append( this.helper ); - - // position helper (lasso) - this.helper.css( { - "left": event.pageX, - "top": event.pageY, - "width": 0, - "height": 0 - } ); - - if ( options.autoRefresh ) { - this.refresh(); - } - - this.selectees.filter( ".ui-selected" ).each( function() { - var selectee = $.data( this, "selectable-item" ); - selectee.startselected = true; - if ( !event.metaKey && !event.ctrlKey ) { - that._removeClass( selectee.$element, "ui-selected" ); - selectee.selected = false; - that._addClass( selectee.$element, "ui-unselecting" ); - selectee.unselecting = true; - - // selectable UNSELECTING callback - that._trigger( "unselecting", event, { - unselecting: selectee.element - } ); - } - } ); - - $( event.target ).parents().addBack().each( function() { - var doSelect, - selectee = $.data( this, "selectable-item" ); - if ( selectee ) { - doSelect = ( !event.metaKey && !event.ctrlKey ) || - !selectee.$element.hasClass( "ui-selected" ); - that._removeClass( selectee.$element, doSelect ? "ui-unselecting" : "ui-selected" ) - ._addClass( selectee.$element, doSelect ? "ui-selecting" : "ui-unselecting" ); - selectee.unselecting = !doSelect; - selectee.selecting = doSelect; - selectee.selected = doSelect; - - // selectable (UN)SELECTING callback - if ( doSelect ) { - that._trigger( "selecting", event, { - selecting: selectee.element - } ); - } else { - that._trigger( "unselecting", event, { - unselecting: selectee.element - } ); - } - return false; - } - } ); - - }, - - _mouseDrag: function( event ) { - - this.dragged = true; - - if ( this.options.disabled ) { - return; - } - - var tmp, - that = this, - options = this.options, - x1 = this.opos[ 0 ], - y1 = this.opos[ 1 ], - x2 = event.pageX, - y2 = event.pageY; - - if ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; } - if ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; } - this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } ); - - this.selectees.each( function() { - var selectee = $.data( this, "selectable-item" ), - hit = false, - offset = {}; - - //prevent helper from being selected if appendTo: selectable - if ( !selectee || selectee.element === that.element[ 0 ] ) { - return; - } - - offset.left = selectee.left + that.elementPos.left; - offset.right = selectee.right + that.elementPos.left; - offset.top = selectee.top + that.elementPos.top; - offset.bottom = selectee.bottom + that.elementPos.top; - - if ( options.tolerance === "touch" ) { - hit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 || - offset.bottom < y1 ) ); - } else if ( options.tolerance === "fit" ) { - hit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 && - offset.bottom < y2 ); - } - - if ( hit ) { - - // SELECT - if ( selectee.selected ) { - that._removeClass( selectee.$element, "ui-selected" ); - selectee.selected = false; - } - if ( selectee.unselecting ) { - that._removeClass( selectee.$element, "ui-unselecting" ); - selectee.unselecting = false; - } - if ( !selectee.selecting ) { - that._addClass( selectee.$element, "ui-selecting" ); - selectee.selecting = true; - - // selectable SELECTING callback - that._trigger( "selecting", event, { - selecting: selectee.element - } ); - } - } else { - - // UNSELECT - if ( selectee.selecting ) { - if ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) { - that._removeClass( selectee.$element, "ui-selecting" ); - selectee.selecting = false; - that._addClass( selectee.$element, "ui-selected" ); - selectee.selected = true; - } else { - that._removeClass( selectee.$element, "ui-selecting" ); - selectee.selecting = false; - if ( selectee.startselected ) { - that._addClass( selectee.$element, "ui-unselecting" ); - selectee.unselecting = true; - } - - // selectable UNSELECTING callback - that._trigger( "unselecting", event, { - unselecting: selectee.element - } ); - } - } - if ( selectee.selected ) { - if ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) { - that._removeClass( selectee.$element, "ui-selected" ); - selectee.selected = false; - - that._addClass( selectee.$element, "ui-unselecting" ); - selectee.unselecting = true; - - // selectable UNSELECTING callback - that._trigger( "unselecting", event, { - unselecting: selectee.element - } ); - } - } - } - } ); - - return false; - }, - - _mouseStop: function( event ) { - var that = this; - - this.dragged = false; - - $( ".ui-unselecting", this.element[ 0 ] ).each( function() { - var selectee = $.data( this, "selectable-item" ); - that._removeClass( selectee.$element, "ui-unselecting" ); - selectee.unselecting = false; - selectee.startselected = false; - that._trigger( "unselected", event, { - unselected: selectee.element - } ); - } ); - $( ".ui-selecting", this.element[ 0 ] ).each( function() { - var selectee = $.data( this, "selectable-item" ); - that._removeClass( selectee.$element, "ui-selecting" ) - ._addClass( selectee.$element, "ui-selected" ); - selectee.selecting = false; - selectee.selected = true; - selectee.startselected = true; - that._trigger( "selected", event, { - selected: selectee.element - } ); - } ); - this._trigger( "stop", event ); - - this.helper.remove(); - - return false; - } - -} ); - - -/*! - * jQuery UI Sortable 1.12.1 + var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, { + version: "1.13.0", + options: { + appendTo: "body", + autoRefresh: true, + distance: 0, + filter: "*", + tolerance: "touch", + + // Callbacks + selected: null, + selecting: null, + start: null, + stop: null, + unselected: null, + unselecting: null + }, + _create: function() { + var that = this; + + this._addClass( "ui-selectable" ); + + this.dragged = false; + + // Cache selectee children based on filter + this.refresh = function() { + that.elementPos = $( that.element[ 0 ] ).offset(); + that.selectees = $( that.options.filter, that.element[ 0 ] ); + that._addClass( that.selectees, "ui-selectee" ); + that.selectees.each( function() { + var $this = $( this ), + selecteeOffset = $this.offset(), + pos = { + left: selecteeOffset.left - that.elementPos.left, + top: selecteeOffset.top - that.elementPos.top + }; + $.data( this, "selectable-item", { + element: this, + $element: $this, + left: pos.left, + top: pos.top, + right: pos.left + $this.outerWidth(), + bottom: pos.top + $this.outerHeight(), + startselected: false, + selected: $this.hasClass( "ui-selected" ), + selecting: $this.hasClass( "ui-selecting" ), + unselecting: $this.hasClass( "ui-unselecting" ) + } ); + } ); + }; + this.refresh(); + + this._mouseInit(); + + this.helper = $( "<div>" ); + this._addClass( this.helper, "ui-selectable-helper" ); + }, + + _destroy: function() { + this.selectees.removeData( "selectable-item" ); + this._mouseDestroy(); + }, + + _mouseStart: function( event ) { + var that = this, + options = this.options; + + this.opos = [ event.pageX, event.pageY ]; + this.elementPos = $( this.element[ 0 ] ).offset(); + + if ( this.options.disabled ) { + return; + } + + this.selectees = $( options.filter, this.element[ 0 ] ); + + this._trigger( "start", event ); + + $( options.appendTo ).append( this.helper ); + + // position helper (lasso) + this.helper.css( { + "left": event.pageX, + "top": event.pageY, + "width": 0, + "height": 0 + } ); + + if ( options.autoRefresh ) { + this.refresh(); + } + + this.selectees.filter( ".ui-selected" ).each( function() { + var selectee = $.data( this, "selectable-item" ); + selectee.startselected = true; + if ( !event.metaKey && !event.ctrlKey ) { + that._removeClass( selectee.$element, "ui-selected" ); + selectee.selected = false; + that._addClass( selectee.$element, "ui-unselecting" ); + selectee.unselecting = true; + + // selectable UNSELECTING callback + that._trigger( "unselecting", event, { + unselecting: selectee.element + } ); + } + } ); + + $( event.target ).parents().addBack().each( function() { + var doSelect, + selectee = $.data( this, "selectable-item" ); + if ( selectee ) { + doSelect = ( !event.metaKey && !event.ctrlKey ) || + !selectee.$element.hasClass( "ui-selected" ); + that._removeClass( selectee.$element, doSelect ? "ui-unselecting" : "ui-selected" ) + ._addClass( selectee.$element, doSelect ? "ui-selecting" : "ui-unselecting" ); + selectee.unselecting = !doSelect; + selectee.selecting = doSelect; + selectee.selected = doSelect; + + // selectable (UN)SELECTING callback + if ( doSelect ) { + that._trigger( "selecting", event, { + selecting: selectee.element + } ); + } else { + that._trigger( "unselecting", event, { + unselecting: selectee.element + } ); + } + return false; + } + } ); + + }, + + _mouseDrag: function( event ) { + + this.dragged = true; + + if ( this.options.disabled ) { + return; + } + + var tmp, + that = this, + options = this.options, + x1 = this.opos[ 0 ], + y1 = this.opos[ 1 ], + x2 = event.pageX, + y2 = event.pageY; + + if ( x1 > x2 ) { + tmp = x2; x2 = x1; x1 = tmp; + } + if ( y1 > y2 ) { + tmp = y2; y2 = y1; y1 = tmp; + } + this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } ); + + this.selectees.each( function() { + var selectee = $.data( this, "selectable-item" ), + hit = false, + offset = {}; + + //prevent helper from being selected if appendTo: selectable + if ( !selectee || selectee.element === that.element[ 0 ] ) { + return; + } + + offset.left = selectee.left + that.elementPos.left; + offset.right = selectee.right + that.elementPos.left; + offset.top = selectee.top + that.elementPos.top; + offset.bottom = selectee.bottom + that.elementPos.top; + + if ( options.tolerance === "touch" ) { + hit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 || + offset.bottom < y1 ) ); + } else if ( options.tolerance === "fit" ) { + hit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 && + offset.bottom < y2 ); + } + + if ( hit ) { + + // SELECT + if ( selectee.selected ) { + that._removeClass( selectee.$element, "ui-selected" ); + selectee.selected = false; + } + if ( selectee.unselecting ) { + that._removeClass( selectee.$element, "ui-unselecting" ); + selectee.unselecting = false; + } + if ( !selectee.selecting ) { + that._addClass( selectee.$element, "ui-selecting" ); + selectee.selecting = true; + + // selectable SELECTING callback + that._trigger( "selecting", event, { + selecting: selectee.element + } ); + } + } else { + + // UNSELECT + if ( selectee.selecting ) { + if ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) { + that._removeClass( selectee.$element, "ui-selecting" ); + selectee.selecting = false; + that._addClass( selectee.$element, "ui-selected" ); + selectee.selected = true; + } else { + that._removeClass( selectee.$element, "ui-selecting" ); + selectee.selecting = false; + if ( selectee.startselected ) { + that._addClass( selectee.$element, "ui-unselecting" ); + selectee.unselecting = true; + } + + // selectable UNSELECTING callback + that._trigger( "unselecting", event, { + unselecting: selectee.element + } ); + } + } + if ( selectee.selected ) { + if ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) { + that._removeClass( selectee.$element, "ui-selected" ); + selectee.selected = false; + + that._addClass( selectee.$element, "ui-unselecting" ); + selectee.unselecting = true; + + // selectable UNSELECTING callback + that._trigger( "unselecting", event, { + unselecting: selectee.element + } ); + } + } + } + } ); + + return false; + }, + + _mouseStop: function( event ) { + var that = this; + + this.dragged = false; + + $( ".ui-unselecting", this.element[ 0 ] ).each( function() { + var selectee = $.data( this, "selectable-item" ); + that._removeClass( selectee.$element, "ui-unselecting" ); + selectee.unselecting = false; + selectee.startselected = false; + that._trigger( "unselected", event, { + unselected: selectee.element + } ); + } ); + $( ".ui-selecting", this.element[ 0 ] ).each( function() { + var selectee = $.data( this, "selectable-item" ); + that._removeClass( selectee.$element, "ui-selecting" ) + ._addClass( selectee.$element, "ui-selected" ); + selectee.selecting = false; + selectee.selected = true; + selectee.startselected = true; + that._trigger( "selected", event, { + selected: selectee.element + } ); + } ); + this._trigger( "stop", event ); + + this.helper.remove(); + + return false; + } + + } ); + + + /*! + * jQuery UI Sortable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -5217,1527 +5254,1585 @@ var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, { //>>css.structure: ../../themes/base/sortable.css - -var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { - version: "1.12.1", - widgetEventPrefix: "sort", - ready: false, - options: { - appendTo: "parent", - axis: false, - connectWith: false, - containment: false, - cursor: "auto", - cursorAt: false, - dropOnEmpty: true, - forcePlaceholderSize: false, - forceHelperSize: false, - grid: false, - handle: false, - helper: "original", - items: "> *", - opacity: false, - placeholder: false, - revert: false, - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - scope: "default", - tolerance: "intersect", - zIndex: 1000, - - // Callbacks - activate: null, - beforeStop: null, - change: null, - deactivate: null, - out: null, - over: null, - receive: null, - remove: null, - sort: null, - start: null, - stop: null, - update: null - }, - - _isOverAxis: function( x, reference, size ) { - return ( x >= reference ) && ( x < ( reference + size ) ); - }, - - _isFloating: function( item ) { - return ( /left|right/ ).test( item.css( "float" ) ) || - ( /inline|table-cell/ ).test( item.css( "display" ) ); - }, - - _create: function() { - this.containerCache = {}; - this._addClass( "ui-sortable" ); - - //Get the items - this.refresh(); - - //Let's determine the parent's offset - this.offset = this.element.offset(); - - //Initialize mouse events for interaction - this._mouseInit(); - - this._setHandleClassName(); - - //We're ready to go - this.ready = true; - - }, - - _setOption: function( key, value ) { - this._super( key, value ); - - if ( key === "handle" ) { - this._setHandleClassName(); - } - }, - - _setHandleClassName: function() { - var that = this; - this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" ); - $.each( this.items, function() { - that._addClass( - this.instance.options.handle ? - this.item.find( this.instance.options.handle ) : - this.item, - "ui-sortable-handle" - ); - } ); - }, - - _destroy: function() { - this._mouseDestroy(); - - for ( var i = this.items.length - 1; i >= 0; i-- ) { - this.items[ i ].item.removeData( this.widgetName + "-item" ); - } - - return this; - }, - - _mouseCapture: function( event, overrideHandle ) { - var currentItem = null, - validHandle = false, - that = this; - - if ( this.reverting ) { - return false; - } - - if ( this.options.disabled || this.options.type === "static" ) { - return false; - } - - //We have to refresh the items data once first - this._refreshItems( event ); - - //Find out if the clicked node (or one of its parents) is a actual item in this.items - $( event.target ).parents().each( function() { - if ( $.data( this, that.widgetName + "-item" ) === that ) { - currentItem = $( this ); - return false; - } - } ); - if ( $.data( event.target, that.widgetName + "-item" ) === that ) { - currentItem = $( event.target ); - } - - if ( !currentItem ) { - return false; - } - if ( this.options.handle && !overrideHandle ) { - $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() { - if ( this === event.target ) { - validHandle = true; - } - } ); - if ( !validHandle ) { - return false; - } - } - - this.currentItem = currentItem; - this._removeCurrentsFromItems(); - return true; - - }, - - _mouseStart: function( event, overrideHandle, noActivation ) { - - var i, body, - o = this.options; - - this.currentContainer = this; - - //We only need to call refreshPositions, because the refreshItems call has been moved to - // mouseCapture - this.refreshPositions(); - - //Create and append the visible helper - this.helper = this._createHelper( event ); - - //Cache the helper size - this._cacheHelperProportions(); - - /* + var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { + version: "1.13.0", + widgetEventPrefix: "sort", + ready: false, + options: { + appendTo: "parent", + axis: false, + connectWith: false, + containment: false, + cursor: "auto", + cursorAt: false, + dropOnEmpty: true, + forcePlaceholderSize: false, + forceHelperSize: false, + grid: false, + handle: false, + helper: "original", + items: "> *", + opacity: false, + placeholder: false, + revert: false, + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + scope: "default", + tolerance: "intersect", + zIndex: 1000, + + // Callbacks + activate: null, + beforeStop: null, + change: null, + deactivate: null, + out: null, + over: null, + receive: null, + remove: null, + sort: null, + start: null, + stop: null, + update: null + }, + + _isOverAxis: function( x, reference, size ) { + return ( x >= reference ) && ( x < ( reference + size ) ); + }, + + _isFloating: function( item ) { + return ( /left|right/ ).test( item.css( "float" ) ) || + ( /inline|table-cell/ ).test( item.css( "display" ) ); + }, + + _create: function() { + this.containerCache = {}; + this._addClass( "ui-sortable" ); + + //Get the items + this.refresh(); + + //Let's determine the parent's offset + this.offset = this.element.offset(); + + //Initialize mouse events for interaction + this._mouseInit(); + + this._setHandleClassName(); + + //We're ready to go + this.ready = true; + + }, + + _setOption: function( key, value ) { + this._super( key, value ); + + if ( key === "handle" ) { + this._setHandleClassName(); + } + }, + + _setHandleClassName: function() { + var that = this; + this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" ); + $.each( this.items, function() { + that._addClass( + this.instance.options.handle ? + this.item.find( this.instance.options.handle ) : + this.item, + "ui-sortable-handle" + ); + } ); + }, + + _destroy: function() { + this._mouseDestroy(); + + for ( var i = this.items.length - 1; i >= 0; i-- ) { + this.items[ i ].item.removeData( this.widgetName + "-item" ); + } + + return this; + }, + + _mouseCapture: function( event, overrideHandle ) { + var currentItem = null, + validHandle = false, + that = this; + + if ( this.reverting ) { + return false; + } + + if ( this.options.disabled || this.options.type === "static" ) { + return false; + } + + //We have to refresh the items data once first + this._refreshItems( event ); + + //Find out if the clicked node (or one of its parents) is a actual item in this.items + $( event.target ).parents().each( function() { + if ( $.data( this, that.widgetName + "-item" ) === that ) { + currentItem = $( this ); + return false; + } + } ); + if ( $.data( event.target, that.widgetName + "-item" ) === that ) { + currentItem = $( event.target ); + } + + if ( !currentItem ) { + return false; + } + if ( this.options.handle && !overrideHandle ) { + $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() { + if ( this === event.target ) { + validHandle = true; + } + } ); + if ( !validHandle ) { + return false; + } + } + + this.currentItem = currentItem; + this._removeCurrentsFromItems(); + return true; + + }, + + _mouseStart: function( event, overrideHandle, noActivation ) { + + var i, body, + o = this.options; + + this.currentContainer = this; + + //We only need to call refreshPositions, because the refreshItems call has been moved to + // mouseCapture + this.refreshPositions(); + + //Prepare the dragged items parent + this.appendTo = $( o.appendTo !== "parent" ? + o.appendTo : + this.currentItem.parent() ); + + //Create and append the visible helper + this.helper = this._createHelper( event ); + + //Cache the helper size + this._cacheHelperProportions(); + + /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ - //Cache the margins of the original element - this._cacheMargins(); - - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - - //The element's absolute position on the page minus margins - this.offset = this.currentItem.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - $.extend( this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - - // This is a relative to absolute position minus the actual position calculation - - // only used for relative positioned helper - relative: this._getRelativeOffset() - } ); - - // Only after we got the offset, we can change the helper's position to absolute - // TODO: Still need to figure out a way to make relative sorting possible - this.helper.css( "position", "absolute" ); - this.cssPosition = this.helper.css( "position" ); - - //Generate the original position - this.originalPosition = this._generatePosition( event ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); - - //Cache the former DOM position - this.domPosition = { - prev: this.currentItem.prev()[ 0 ], - parent: this.currentItem.parent()[ 0 ] - }; - - // If the helper is not the original, hide the original so it's not playing any role during - // the drag, won't cause anything bad this way - if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { - this.currentItem.hide(); - } - - //Create the placeholder - this._createPlaceholder(); - - //Set a containment if given in the options - if ( o.containment ) { - this._setContainment(); - } - - if ( o.cursor && o.cursor !== "auto" ) { // cursor option - body = this.document.find( "body" ); - - // Support: IE - this.storedCursor = body.css( "cursor" ); - body.css( "cursor", o.cursor ); - - this.storedStylesheet = - $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body ); - } - - if ( o.opacity ) { // opacity option - if ( this.helper.css( "opacity" ) ) { - this._storedOpacity = this.helper.css( "opacity" ); - } - this.helper.css( "opacity", o.opacity ); - } - - if ( o.zIndex ) { // zIndex option - if ( this.helper.css( "zIndex" ) ) { - this._storedZIndex = this.helper.css( "zIndex" ); - } - this.helper.css( "zIndex", o.zIndex ); - } - - //Prepare scrolling - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { - this.overflowOffset = this.scrollParent.offset(); - } - - //Call callbacks - this._trigger( "start", event, this._uiHash() ); - - //Recache the helper size - if ( !this._preserveHelperProportions ) { - this._cacheHelperProportions(); - } - - //Post "activate" events to possible containers - if ( !noActivation ) { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); - } - } - - //Prepare possible droppables - if ( $.ui.ddmanager ) { - $.ui.ddmanager.current = this; - } - - if ( $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); - } - - this.dragging = true; - - this._addClass( this.helper, "ui-sortable-helper" ); - - // Execute the drag once - this causes the helper not to be visiblebefore getting its - // correct position - this._mouseDrag( event ); - return true; - - }, - - _mouseDrag: function( event ) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; - - //Compute the helpers position - this.position = this._generatePosition( event ); - this.positionAbs = this._convertPositionTo( "absolute" ); - - if ( !this.lastPositionAbs ) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if ( this.options.scroll ) { - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { - - if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - - event.pageY < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; - } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; - } - - if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - - event.pageX < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; - } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; - } - - } else { - - if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); - } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); - } - - if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() - o.scrollSpeed - ); - } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() + o.scrollSpeed - ); - } - - } - - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); - } - } - - //Regenerate the absolute position used for position checks - this.positionAbs = this._convertPositionTo( "absolute" ); - - //Set the helper position - if ( !this.options.axis || this.options.axis !== "y" ) { - this.helper[ 0 ].style.left = this.position.left + "px"; - } - if ( !this.options.axis || this.options.axis !== "x" ) { - this.helper[ 0 ].style.top = this.position.top + "px"; - } - - //Rearrange - for ( i = this.items.length - 1; i >= 0; i-- ) { - - //Cache variables and intersection, continue if no intersection - item = this.items[ i ]; - itemElement = item.item[ 0 ]; - intersection = this._intersectsWithPointer( item ); - if ( !intersection ) { - continue; - } - - // Only put the placeholder inside the current Container, skip all - // items from other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this, moving items in "sub-sortables" can cause - // the placeholder to jitter between the outer and inner container. - if ( item.instance !== this.currentContainer ) { - continue; - } - - // Cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if ( itemElement !== this.currentItem[ 0 ] && - this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && - !$.contains( this.placeholder[ 0 ], itemElement ) && - ( this.options.type === "semi-dynamic" ? - !$.contains( this.element[ 0 ], itemElement ) : - true - ) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { - this._rearrange( event, item ); - } else { - break; - } - - this._trigger( "change", event, this._uiHash() ); - break; - } - } - - //Post events to containers - this._contactContainers( event ); - - //Interconnect with droppables - if ( $.ui.ddmanager ) { - $.ui.ddmanager.drag( this, event ); - } - - //Call callbacks - this._trigger( "sort", event, this._uiHash() ); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function( event, noPropagation ) { - - if ( !event ) { - return; - } - - //If we are using droppables, inform the manager about the drop - if ( $.ui.ddmanager && !this.options.dropBehaviour ) { - $.ui.ddmanager.drop( this, event ); - } - - if ( this.options.revert ) { - var that = this, - cur = this.placeholder.offset(), - axis = this.options.axis, - animation = {}; - - if ( !axis || axis === "x" ) { - animation.left = cur.left - this.offset.parent.left - this.margins.left + - ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? - 0 : - this.offsetParent[ 0 ].scrollLeft - ); - } - if ( !axis || axis === "y" ) { - animation.top = cur.top - this.offset.parent.top - this.margins.top + - ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? - 0 : - this.offsetParent[ 0 ].scrollTop - ); - } - this.reverting = true; - $( this.helper ).animate( - animation, - parseInt( this.options.revert, 10 ) || 500, - function() { - that._clear( event ); - } - ); - } else { - this._clear( event, noPropagation ); - } - - return false; - - }, - - cancel: function() { - - if ( this.dragging ) { - - this._mouseUp( new $.Event( "mouseup", { target: null } ) ); - - if ( this.options.helper === "original" ) { - this.currentItem.css( this._storedCSS ); - this._removeClass( this.currentItem, "ui-sortable-helper" ); - } else { - this.currentItem.show(); - } - - //Post deactivating events to containers - for ( var i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) ); - if ( this.containers[ i ].containerCache.over ) { - this.containers[ i ]._trigger( "out", null, this._uiHash( this ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - } - - if ( this.placeholder ) { - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, - // it unbinds ALL events from the original node! - if ( this.placeholder[ 0 ].parentNode ) { - this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); - } - if ( this.options.helper !== "original" && this.helper && - this.helper[ 0 ].parentNode ) { - this.helper.remove(); - } - - $.extend( this, { - helper: null, - dragging: false, - reverting: false, - _noFinalSort: null - } ); - - if ( this.domPosition.prev ) { - $( this.domPosition.prev ).after( this.currentItem ); - } else { - $( this.domPosition.parent ).prepend( this.currentItem ); - } - } - - return this; - - }, - - serialize: function( o ) { - - var items = this._getItemsAsjQuery( o && o.connected ), - str = []; - o = o || {}; - - $( items ).each( function() { - var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" ) - .match( o.expression || ( /(.+)[\-=_](.+)/ ) ); - if ( res ) { - str.push( - ( o.key || res[ 1 ] + "[]" ) + - "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) ); - } - } ); - - if ( !str.length && o.key ) { - str.push( o.key + "=" ); - } - - return str.join( "&" ); - - }, - - toArray: function( o ) { - - var items = this._getItemsAsjQuery( o && o.connected ), - ret = []; - - o = o || {}; - - items.each( function() { - ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" ); - } ); - return ret; - - }, - - /* Be careful with the following core functions */ - _intersectsWith: function( item ) { - - var x1 = this.positionAbs.left, - x2 = x1 + this.helperProportions.width, - y1 = this.positionAbs.top, - y2 = y1 + this.helperProportions.height, - l = item.left, - r = l + item.width, - t = item.top, - b = t + item.height, - dyClick = this.offset.click.top, - dxClick = this.offset.click.left, - isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && - ( y1 + dyClick ) < b ), - isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && - ( x1 + dxClick ) < r ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( this.options.tolerance === "pointer" || - this.options.forcePointerForContainers || - ( this.options.tolerance !== "pointer" && - this.helperProportions[ this.floating ? "width" : "height" ] > - item[ this.floating ? "width" : "height" ] ) - ) { - return isOverElement; - } else { - - return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half - x2 - ( this.helperProportions.width / 2 ) < r && // Left Half - t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half - y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half - - } - }, - - _intersectsWithPointer: function( item ) { - var verticalDirection, horizontalDirection, - isOverElementHeight = ( this.options.axis === "x" ) || - this._isOverAxis( - this.positionAbs.top + this.offset.click.top, item.top, item.height ), - isOverElementWidth = ( this.options.axis === "y" ) || - this._isOverAxis( - this.positionAbs.left + this.offset.click.left, item.left, item.width ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( !isOverElement ) { - return false; - } - - verticalDirection = this._getDragVerticalDirection(); - horizontalDirection = this._getDragHorizontalDirection(); - - return this.floating ? - ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) - : ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); - - }, - - _intersectsWithSides: function( item ) { - - var isOverBottomHalf = this._isOverAxis( this.positionAbs.top + - this.offset.click.top, item.top + ( item.height / 2 ), item.height ), - isOverRightHalf = this._isOverAxis( this.positionAbs.left + - this.offset.click.left, item.left + ( item.width / 2 ), item.width ), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if ( this.floating && horizontalDirection ) { - return ( ( horizontalDirection === "right" && isOverRightHalf ) || - ( horizontalDirection === "left" && !isOverRightHalf ) ); - } else { - return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) || - ( verticalDirection === "up" && !isOverBottomHalf ) ); - } - - }, - - _getDragVerticalDirection: function() { - var delta = this.positionAbs.top - this.lastPositionAbs.top; - return delta !== 0 && ( delta > 0 ? "down" : "up" ); - }, - - _getDragHorizontalDirection: function() { - var delta = this.positionAbs.left - this.lastPositionAbs.left; - return delta !== 0 && ( delta > 0 ? "right" : "left" ); - }, - - refresh: function( event ) { - this._refreshItems( event ); - this._setHandleClassName(); - this.refreshPositions(); - return this; - }, - - _connectWith: function() { - var options = this.options; - return options.connectWith.constructor === String ? - [ options.connectWith ] : - options.connectWith; - }, - - _getItemsAsjQuery: function( connected ) { - - var i, j, cur, inst, - items = [], - queries = [], - connectWith = this._connectWith(); - - if ( connectWith && connected ) { - for ( i = connectWith.length - 1; i >= 0; i-- ) { - cur = $( connectWith[ i ], this.document[ 0 ] ); - for ( j = cur.length - 1; j >= 0; j-- ) { - inst = $.data( cur[ j ], this.widgetFullName ); - if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? - inst.options.items.call( inst.element ) : - $( inst.options.items, inst.element ) - .not( ".ui-sortable-helper" ) - .not( ".ui-sortable-placeholder" ), inst ] ); - } - } - } - } - - queries.push( [ $.isFunction( this.options.items ) ? - this.options.items - .call( this.element, null, { options: this.options, item: this.currentItem } ) : - $( this.options.items, this.element ) - .not( ".ui-sortable-helper" ) - .not( ".ui-sortable-placeholder" ), this ] ); - - function addItems() { - items.push( this ); - } - for ( i = queries.length - 1; i >= 0; i-- ) { - queries[ i ][ 0 ].each( addItems ); - } - - return $( items ); - - }, - - _removeCurrentsFromItems: function() { - - var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" ); - - this.items = $.grep( this.items, function( item ) { - for ( var j = 0; j < list.length; j++ ) { - if ( list[ j ] === item.item[ 0 ] ) { - return false; - } - } - return true; - } ); - - }, - - _refreshItems: function( event ) { - - this.items = []; - this.containers = [ this ]; - - var i, j, cur, inst, targetData, _queries, item, queriesLength, - items = this.items, - queries = [ [ $.isFunction( this.options.items ) ? - this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : - $( this.options.items, this.element ), this ] ], - connectWith = this._connectWith(); - - //Shouldn't be run the first time through due to massive slow-down - if ( connectWith && this.ready ) { - for ( i = connectWith.length - 1; i >= 0; i-- ) { - cur = $( connectWith[ i ], this.document[ 0 ] ); - for ( j = cur.length - 1; j >= 0; j-- ) { - inst = $.data( cur[ j ], this.widgetFullName ); - if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? - inst.options.items - .call( inst.element[ 0 ], event, { item: this.currentItem } ) : - $( inst.options.items, inst.element ), inst ] ); - this.containers.push( inst ); - } - } - } - } - - for ( i = queries.length - 1; i >= 0; i-- ) { - targetData = queries[ i ][ 1 ]; - _queries = queries[ i ][ 0 ]; - - for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) { - item = $( _queries[ j ] ); - - // Data for target checking (mouse manager) - item.data( this.widgetName + "-item", targetData ); - - items.push( { - item: item, - instance: targetData, - width: 0, height: 0, - left: 0, top: 0 - } ); - } - } - - }, - - refreshPositions: function( fast ) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, - // the offsetParent's position will change - if ( this.offsetParent && this.helper ) { - this.offset.parent = this._getParentOffset(); - } - - var i, item, t, p; - - for ( i = this.items.length - 1; i >= 0; i-- ) { - item = this.items[ i ]; - - //We ignore calculating positions of all connected containers when we're not over them - if ( item.instance !== this.currentContainer && this.currentContainer && - item.item[ 0 ] !== this.currentItem[ 0 ] ) { - continue; - } - - t = this.options.toleranceElement ? - $( this.options.toleranceElement, item.item ) : - item.item; - - if ( !fast ) { - item.width = t.outerWidth(); - item.height = t.outerHeight(); - } - - p = t.offset(); - item.left = p.left; - item.top = p.top; - } - - if ( this.options.custom && this.options.custom.refreshContainers ) { - this.options.custom.refreshContainers.call( this ); - } else { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - p = this.containers[ i ].element.offset(); - this.containers[ i ].containerCache.left = p.left; - this.containers[ i ].containerCache.top = p.top; - this.containers[ i ].containerCache.width = - this.containers[ i ].element.outerWidth(); - this.containers[ i ].containerCache.height = - this.containers[ i ].element.outerHeight(); - } - } - - return this; - }, - - _createPlaceholder: function( that ) { - that = that || this; - var className, - o = that.options; - - if ( !o.placeholder || o.placeholder.constructor === String ) { - className = o.placeholder; - o.placeholder = { - element: function() { - - var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(), - element = $( "<" + nodeName + ">", that.document[ 0 ] ); - - that._addClass( element, "ui-sortable-placeholder", - className || that.currentItem[ 0 ].className ) - ._removeClass( element, "ui-sortable-helper" ); - - if ( nodeName === "tbody" ) { - that._createTrPlaceholder( - that.currentItem.find( "tr" ).eq( 0 ), - $( "<tr>", that.document[ 0 ] ).appendTo( element ) - ); - } else if ( nodeName === "tr" ) { - that._createTrPlaceholder( that.currentItem, element ); - } else if ( nodeName === "img" ) { - element.attr( "src", that.currentItem.attr( "src" ) ); - } - - if ( !className ) { - element.css( "visibility", "hidden" ); - } - - return element; - }, - update: function( container, p ) { - - // 1. If a className is set as 'placeholder option, we don't force sizes - - // the class is responsible for that - // 2. The option 'forcePlaceholderSize can be enabled to force it even if a - // class name is specified - if ( className && !o.forcePlaceholderSize ) { - return; - } - - //If the element doesn't have a actual height by itself (without styles coming - // from a stylesheet), it receives the inline height from the dragged item - if ( !p.height() ) { - p.height( - that.currentItem.innerHeight() - - parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) - - parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) ); - } - if ( !p.width() ) { - p.width( - that.currentItem.innerWidth() - - parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) - - parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) ); - } - } - }; - } - - //Create the placeholder - that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) ); - - //Append it after the actual current item - that.currentItem.after( that.placeholder ); - - //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) - o.placeholder.update( that, that.placeholder ); - - }, - - _createTrPlaceholder: function( sourceTr, targetTr ) { - var that = this; - - sourceTr.children().each( function() { - $( "<td> </td>", that.document[ 0 ] ) - .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) - .appendTo( targetTr ); - } ); - }, - - _contactContainers: function( event ) { - var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, - floating, axis, - innermostContainer = null, - innermostIndex = null; - - // Get innermost container that intersects with item - for ( i = this.containers.length - 1; i >= 0; i-- ) { - - // Never consider a container that's located within the item itself - if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) { - continue; - } - - if ( this._intersectsWith( this.containers[ i ].containerCache ) ) { - - // If we've already found a container and it's more "inner" than this, then continue - if ( innermostContainer && - $.contains( - this.containers[ i ].element[ 0 ], - innermostContainer.element[ 0 ] ) ) { - continue; - } - - innermostContainer = this.containers[ i ]; - innermostIndex = i; - - } else { - - // container doesn't intersect. trigger "out" event if necessary - if ( this.containers[ i ].containerCache.over ) { - this.containers[ i ]._trigger( "out", event, this._uiHash( this ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - } - - // If no intersecting containers found, return - if ( !innermostContainer ) { - return; - } - - // Move the item into the container if it's not there already - if ( this.containers.length === 1 ) { - if ( !this.containers[ innermostIndex ].containerCache.over ) { - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); - this.containers[ innermostIndex ].containerCache.over = 1; - } - } else { - - // When entering a new container, we will find the item with the least distance and - // append our item near it - dist = 10000; - itemWithLeastDistance = null; - floating = innermostContainer.floating || this._isFloating( this.currentItem ); - posProperty = floating ? "left" : "top"; - sizeProperty = floating ? "width" : "height"; - axis = floating ? "pageX" : "pageY"; - - for ( j = this.items.length - 1; j >= 0; j-- ) { - if ( !$.contains( - this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] ) - ) { - continue; - } - if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) { - continue; - } - - cur = this.items[ j ].item.offset()[ posProperty ]; - nearBottom = false; - if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) { - nearBottom = true; - } - - if ( Math.abs( event[ axis ] - cur ) < dist ) { - dist = Math.abs( event[ axis ] - cur ); - itemWithLeastDistance = this.items[ j ]; - this.direction = nearBottom ? "up" : "down"; - } - } - - //Check if dropOnEmpty is enabled - if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) { - return; - } - - if ( this.currentContainer === this.containers[ innermostIndex ] ) { - if ( !this.currentContainer.containerCache.over ) { - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() ); - this.currentContainer.containerCache.over = 1; - } - return; - } - - itemWithLeastDistance ? - this._rearrange( event, itemWithLeastDistance, null, true ) : - this._rearrange( event, null, this.containers[ innermostIndex ].element, true ); - this._trigger( "change", event, this._uiHash() ); - this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) ); - this.currentContainer = this.containers[ innermostIndex ]; - - //Update the placeholder - this.options.placeholder.update( this.currentContainer, this.placeholder ); - - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); - this.containers[ innermostIndex ].containerCache.over = 1; - } - - }, - - _createHelper: function( event ) { - - var o = this.options, - helper = $.isFunction( o.helper ) ? - $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) : - ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem ); - - //Add the helper to the DOM if that didn't happen already - if ( !helper.parents( "body" ).length ) { - $( o.appendTo !== "parent" ? - o.appendTo : - this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); - } - - if ( helper[ 0 ] === this.currentItem[ 0 ] ) { - this._storedCSS = { - width: this.currentItem[ 0 ].style.width, - height: this.currentItem[ 0 ].style.height, - position: this.currentItem.css( "position" ), - top: this.currentItem.css( "top" ), - left: this.currentItem.css( "left" ) - }; - } - - if ( !helper[ 0 ].style.width || o.forceHelperSize ) { - helper.width( this.currentItem.width() ); - } - if ( !helper[ 0 ].style.height || o.forceHelperSize ) { - helper.height( this.currentItem.height() ); - } - - return helper; - - }, - - _adjustOffsetFromHelper: function( obj ) { - if ( typeof obj === "string" ) { - obj = obj.split( " " ); - } - if ( $.isArray( obj ) ) { - obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; - } - if ( "left" in obj ) { - this.offset.click.left = obj.left + this.margins.left; - } - if ( "right" in obj ) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ( "top" in obj ) { - this.offset.click.top = obj.top + this.margins.top; - } - if ( "bottom" in obj ) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the - // following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the - // next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't - // the document, which means that the scroll is included in the initial calculation of the - // offset of the parent, and never recalculated upon drag - if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - // This needs to be actually done for all browsers, since pageX/pageY includes this - // information with an ugly IE fix - if ( this.offsetParent[ 0 ] === this.document[ 0 ].body || - ( this.offsetParent[ 0 ].tagName && - this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), - left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) - }; - - }, - - _getRelativeOffset: function() { - - if ( this.cssPosition === "relative" ) { - var p = this.currentItem.position(); - return { - top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + - this.scrollParent.scrollTop(), - left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + - this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ), - top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 ) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var ce, co, over, - o = this.options; - if ( o.containment === "parent" ) { - o.containment = this.helper[ 0 ].parentNode; - } - if ( o.containment === "document" || o.containment === "window" ) { - this.containment = [ - 0 - this.offset.relative.left - this.offset.parent.left, - 0 - this.offset.relative.top - this.offset.parent.top, - o.containment === "document" ? - this.document.width() : - this.window.width() - this.helperProportions.width - this.margins.left, - ( o.containment === "document" ? - ( this.document.height() || document.body.parentNode.scrollHeight ) : - this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight - ) - this.helperProportions.height - this.margins.top - ]; - } - - if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) { - ce = $( o.containment )[ 0 ]; - co = $( o.containment ).offset(); - over = ( $( ce ).css( "overflow" ) !== "hidden" ); - - this.containment = [ - co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) + - ( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left, - co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) + - ( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top, - co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - - ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) - - ( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) - - this.helperProportions.width - this.margins.left, - co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - - ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) - - ( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) - - this.helperProportions.height - this.margins.top - ]; - } - - }, - - _convertPositionTo: function( d, pos ) { - - if ( !pos ) { - pos = this.position; - } - var mod = d === "absolute" ? 1 : -1, - scroll = this.cssPosition === "absolute" && - !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? - this.offsetParent : - this.scrollParent, - scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); - - return { - top: ( - - // The absolute mouse position - pos.top + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top * mod - - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollTop() : - ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod ) - ), - left: ( - - // The absolute mouse position - pos.left + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left * mod - - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : - scroll.scrollLeft() ) * mod ) - ) - }; - - }, - - _generatePosition: function( event ) { - - var top, left, - o = this.options, - pageX = event.pageX, - pageY = event.pageY, - scroll = this.cssPosition === "absolute" && - !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? - this.offsetParent : - this.scrollParent, - scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); - - // This is another very weird special case that only happens for relative elements: - // 1. If the css position is relative - // 2. and the scroll parent is the document or similar to the offset parent - // we have to refresh the relative offset during the scroll so there are no jumps - if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) { - this.offset.relative = this._getRelativeOffset(); - } - - /* + //Cache the margins of the original element + this._cacheMargins(); + + //The element's absolute position on the page minus margins + this.offset = this.currentItem.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend( this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + + // This is a relative to absolute position minus the actual position calculation - + // only used for relative positioned helper + relative: this._getRelativeOffset() + } ); + + // After we get the helper offset, but before we get the parent offset we can + // change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css( "position", "absolute" ); + this.cssPosition = this.helper.css( "position" ); + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + if ( o.cursorAt ) { + this._adjustOffsetFromHelper( o.cursorAt ); + } + + //Cache the former DOM position + this.domPosition = { + prev: this.currentItem.prev()[ 0 ], + parent: this.currentItem.parent()[ 0 ] + }; + + // If the helper is not the original, hide the original so it's not playing any role during + // the drag, won't cause anything bad this way + if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { + this.currentItem.hide(); + } + + //Create the placeholder + this._createPlaceholder(); + + //Get the next scrolling parent + this.scrollParent = this.placeholder.scrollParent(); + + $.extend( this.offset, { + parent: this._getParentOffset() + } ); + + //Set a containment if given in the options + if ( o.containment ) { + this._setContainment(); + } + + if ( o.cursor && o.cursor !== "auto" ) { // cursor option + body = this.document.find( "body" ); + + // Support: IE + this.storedCursor = body.css( "cursor" ); + body.css( "cursor", o.cursor ); + + this.storedStylesheet = + $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body ); + } + + // We need to make sure to grab the zIndex before setting the + // opacity, because setting the opacity to anything lower than 1 + // causes the zIndex to change from "auto" to 0. + if ( o.zIndex ) { // zIndex option + if ( this.helper.css( "zIndex" ) ) { + this._storedZIndex = this.helper.css( "zIndex" ); + } + this.helper.css( "zIndex", o.zIndex ); + } + + if ( o.opacity ) { // opacity option + if ( this.helper.css( "opacity" ) ) { + this._storedOpacity = this.helper.css( "opacity" ); + } + this.helper.css( "opacity", o.opacity ); + } + + //Prepare scrolling + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + this.overflowOffset = this.scrollParent.offset(); + } + + //Call callbacks + this._trigger( "start", event, this._uiHash() ); + + //Recache the helper size + if ( !this._preserveHelperProportions ) { + this._cacheHelperProportions(); + } + + //Post "activate" events to possible containers + if ( !noActivation ) { + for ( i = this.containers.length - 1; i >= 0; i-- ) { + this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); + } + } + + //Prepare possible droppables + if ( $.ui.ddmanager ) { + $.ui.ddmanager.current = this; + } + + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + + this.dragging = true; + + this._addClass( this.helper, "ui-sortable-helper" ); + + //Move the helper, if needed + if ( !this.helper.parent().is( this.appendTo ) ) { + this.helper.detach().appendTo( this.appendTo ); + + //Update position + this.offset.parent = this._getParentOffset(); + } + + //Generate the original position + this.position = this.originalPosition = this._generatePosition( event ); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" ); + + this._mouseDrag( event ); + + return true; + + }, + + _scroll: function( event ) { + var o = this.options, + scrolled = false; + + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + + if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - + event.pageY < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; + } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; + } + + if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - + event.pageX < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; + } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; + } + + } else { + + if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); + } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); + } + + if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() - o.scrollSpeed + ); + } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() + o.scrollSpeed + ); + } + + } + + return scrolled; + }, + + _mouseDrag: function( event ) { + var i, item, itemElement, intersection, + o = this.options; + + //Compute the helpers position + this.position = this._generatePosition( event ); + this.positionAbs = this._convertPositionTo( "absolute" ); + + //Set the helper position + if ( !this.options.axis || this.options.axis !== "y" ) { + this.helper[ 0 ].style.left = this.position.left + "px"; + } + if ( !this.options.axis || this.options.axis !== "x" ) { + this.helper[ 0 ].style.top = this.position.top + "px"; + } + + //Post events to containers + this._contactContainers( event ); + + if ( this.innermostContainer !== null ) { + + //Do scrolling + if ( o.scroll ) { + if ( this._scroll( event ) !== false ) { + + //Update item positions used in position checks + this._refreshItemPositions( true ); + + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + } + } + + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + + //Rearrange + for ( i = this.items.length - 1; i >= 0; i-- ) { + + //Cache variables and intersection, continue if no intersection + item = this.items[ i ]; + itemElement = item.item[ 0 ]; + intersection = this._intersectsWithPointer( item ); + if ( !intersection ) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items from other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this, moving items in "sub-sortables" can cause + // the placeholder to jitter between the outer and inner container. + if ( item.instance !== this.currentContainer ) { + continue; + } + + // Cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if ( itemElement !== this.currentItem[ 0 ] && + this.placeholder[ intersection === 1 ? + "next" : "prev" ]()[ 0 ] !== itemElement && + !$.contains( this.placeholder[ 0 ], itemElement ) && + ( this.options.type === "semi-dynamic" ? + !$.contains( this.element[ 0 ], itemElement ) : + true + ) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if ( this.options.tolerance === "pointer" || + this._intersectsWithSides( item ) ) { + this._rearrange( event, item ); + } else { + break; + } + + this._trigger( "change", event, this._uiHash() ); + break; + } + } + } + + //Interconnect with droppables + if ( $.ui.ddmanager ) { + $.ui.ddmanager.drag( this, event ); + } + + //Call callbacks + this._trigger( "sort", event, this._uiHash() ); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function( event, noPropagation ) { + + if ( !event ) { + return; + } + + //If we are using droppables, inform the manager about the drop + if ( $.ui.ddmanager && !this.options.dropBehaviour ) { + $.ui.ddmanager.drop( this, event ); + } + + if ( this.options.revert ) { + var that = this, + cur = this.placeholder.offset(), + axis = this.options.axis, + animation = {}; + + if ( !axis || axis === "x" ) { + animation.left = cur.left - this.offset.parent.left - this.margins.left + + ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? + 0 : + this.offsetParent[ 0 ].scrollLeft + ); + } + if ( !axis || axis === "y" ) { + animation.top = cur.top - this.offset.parent.top - this.margins.top + + ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? + 0 : + this.offsetParent[ 0 ].scrollTop + ); + } + this.reverting = true; + $( this.helper ).animate( + animation, + parseInt( this.options.revert, 10 ) || 500, + function() { + that._clear( event ); + } + ); + } else { + this._clear( event, noPropagation ); + } + + return false; + + }, + + cancel: function() { + + if ( this.dragging ) { + + this._mouseUp( new $.Event( "mouseup", { target: null } ) ); + + if ( this.options.helper === "original" ) { + this.currentItem.css( this._storedCSS ); + this._removeClass( this.currentItem, "ui-sortable-helper" ); + } else { + this.currentItem.show(); + } + + //Post deactivating events to containers + for ( var i = this.containers.length - 1; i >= 0; i-- ) { + this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) ); + if ( this.containers[ i ].containerCache.over ) { + this.containers[ i ]._trigger( "out", null, this._uiHash( this ) ); + this.containers[ i ].containerCache.over = 0; + } + } + + } + + if ( this.placeholder ) { + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, + // it unbinds ALL events from the original node! + if ( this.placeholder[ 0 ].parentNode ) { + this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); + } + if ( this.options.helper !== "original" && this.helper && + this.helper[ 0 ].parentNode ) { + this.helper.remove(); + } + + $.extend( this, { + helper: null, + dragging: false, + reverting: false, + _noFinalSort: null + } ); + + if ( this.domPosition.prev ) { + $( this.domPosition.prev ).after( this.currentItem ); + } else { + $( this.domPosition.parent ).prepend( this.currentItem ); + } + } + + return this; + + }, + + serialize: function( o ) { + + var items = this._getItemsAsjQuery( o && o.connected ), + str = []; + o = o || {}; + + $( items ).each( function() { + var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" ) + .match( o.expression || ( /(.+)[\-=_](.+)/ ) ); + if ( res ) { + str.push( + ( o.key || res[ 1 ] + "[]" ) + + "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) ); + } + } ); + + if ( !str.length && o.key ) { + str.push( o.key + "=" ); + } + + return str.join( "&" ); + + }, + + toArray: function( o ) { + + var items = this._getItemsAsjQuery( o && o.connected ), + ret = []; + + o = o || {}; + + items.each( function() { + ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" ); + } ); + return ret; + + }, + + /* Be careful with the following core functions */ + _intersectsWith: function( item ) { + + var x1 = this.positionAbs.left, + x2 = x1 + this.helperProportions.width, + y1 = this.positionAbs.top, + y2 = y1 + this.helperProportions.height, + l = item.left, + r = l + item.width, + t = item.top, + b = t + item.height, + dyClick = this.offset.click.top, + dxClick = this.offset.click.left, + isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && + ( y1 + dyClick ) < b ), + isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && + ( x1 + dxClick ) < r ), + isOverElement = isOverElementHeight && isOverElementWidth; + + if ( this.options.tolerance === "pointer" || + this.options.forcePointerForContainers || + ( this.options.tolerance !== "pointer" && + this.helperProportions[ this.floating ? "width" : "height" ] > + item[ this.floating ? "width" : "height" ] ) + ) { + return isOverElement; + } else { + + return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half + x2 - ( this.helperProportions.width / 2 ) < r && // Left Half + t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half + y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half + + } + }, + + _intersectsWithPointer: function( item ) { + var verticalDirection, horizontalDirection, + isOverElementHeight = ( this.options.axis === "x" ) || + this._isOverAxis( + this.positionAbs.top + this.offset.click.top, item.top, item.height ), + isOverElementWidth = ( this.options.axis === "y" ) || + this._isOverAxis( + this.positionAbs.left + this.offset.click.left, item.left, item.width ), + isOverElement = isOverElementHeight && isOverElementWidth; + + if ( !isOverElement ) { + return false; + } + + verticalDirection = this.dragDirection.vertical; + horizontalDirection = this.dragDirection.horizontal; + + return this.floating ? + ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) : + ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); + + }, + + _intersectsWithSides: function( item ) { + + var isOverBottomHalf = this._isOverAxis( this.positionAbs.top + + this.offset.click.top, item.top + ( item.height / 2 ), item.height ), + isOverRightHalf = this._isOverAxis( this.positionAbs.left + + this.offset.click.left, item.left + ( item.width / 2 ), item.width ), + verticalDirection = this.dragDirection.vertical, + horizontalDirection = this.dragDirection.horizontal; + + if ( this.floating && horizontalDirection ) { + return ( ( horizontalDirection === "right" && isOverRightHalf ) || + ( horizontalDirection === "left" && !isOverRightHalf ) ); + } else { + return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) || + ( verticalDirection === "up" && !isOverBottomHalf ) ); + } + + }, + + _getDragVerticalDirection: function() { + var delta = this.positionAbs.top - this.lastPositionAbs.top; + return delta !== 0 && ( delta > 0 ? "down" : "up" ); + }, + + _getDragHorizontalDirection: function() { + var delta = this.positionAbs.left - this.lastPositionAbs.left; + return delta !== 0 && ( delta > 0 ? "right" : "left" ); + }, + + refresh: function( event ) { + this._refreshItems( event ); + this._setHandleClassName(); + this.refreshPositions(); + return this; + }, + + _connectWith: function() { + var options = this.options; + return options.connectWith.constructor === String ? + [ options.connectWith ] : + options.connectWith; + }, + + _getItemsAsjQuery: function( connected ) { + + var i, j, cur, inst, + items = [], + queries = [], + connectWith = this._connectWith(); + + if ( connectWith && connected ) { + for ( i = connectWith.length - 1; i >= 0; i-- ) { + cur = $( connectWith[ i ], this.document[ 0 ] ); + for ( j = cur.length - 1; j >= 0; j-- ) { + inst = $.data( cur[ j ], this.widgetFullName ); + if ( inst && inst !== this && !inst.options.disabled ) { + queries.push( [ typeof inst.options.items === "function" ? + inst.options.items.call( inst.element ) : + $( inst.options.items, inst.element ) + .not( ".ui-sortable-helper" ) + .not( ".ui-sortable-placeholder" ), inst ] ); + } + } + } + } + + queries.push( [ typeof this.options.items === "function" ? + this.options.items + .call( this.element, null, { options: this.options, item: this.currentItem } ) : + $( this.options.items, this.element ) + .not( ".ui-sortable-helper" ) + .not( ".ui-sortable-placeholder" ), this ] ); + + function addItems() { + items.push( this ); + } + for ( i = queries.length - 1; i >= 0; i-- ) { + queries[ i ][ 0 ].each( addItems ); + } + + return $( items ); + + }, + + _removeCurrentsFromItems: function() { + + var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" ); + + this.items = $.grep( this.items, function( item ) { + for ( var j = 0; j < list.length; j++ ) { + if ( list[ j ] === item.item[ 0 ] ) { + return false; + } + } + return true; + } ); + + }, + + _refreshItems: function( event ) { + + this.items = []; + this.containers = [ this ]; + + var i, j, cur, inst, targetData, _queries, item, queriesLength, + items = this.items, + queries = [ [ typeof this.options.items === "function" ? + this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : + $( this.options.items, this.element ), this ] ], + connectWith = this._connectWith(); + + //Shouldn't be run the first time through due to massive slow-down + if ( connectWith && this.ready ) { + for ( i = connectWith.length - 1; i >= 0; i-- ) { + cur = $( connectWith[ i ], this.document[ 0 ] ); + for ( j = cur.length - 1; j >= 0; j-- ) { + inst = $.data( cur[ j ], this.widgetFullName ); + if ( inst && inst !== this && !inst.options.disabled ) { + queries.push( [ typeof inst.options.items === "function" ? + inst.options.items + .call( inst.element[ 0 ], event, { item: this.currentItem } ) : + $( inst.options.items, inst.element ), inst ] ); + this.containers.push( inst ); + } + } + } + } + + for ( i = queries.length - 1; i >= 0; i-- ) { + targetData = queries[ i ][ 1 ]; + _queries = queries[ i ][ 0 ]; + + for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) { + item = $( _queries[ j ] ); + + // Data for target checking (mouse manager) + item.data( this.widgetName + "-item", targetData ); + + items.push( { + item: item, + instance: targetData, + width: 0, height: 0, + left: 0, top: 0 + } ); + } + } + + }, + + _refreshItemPositions: function( fast ) { + var i, item, t, p; + + for ( i = this.items.length - 1; i >= 0; i-- ) { + item = this.items[ i ]; + + //We ignore calculating positions of all connected containers when we're not over them + if ( this.currentContainer && item.instance !== this.currentContainer && + item.item[ 0 ] !== this.currentItem[ 0 ] ) { + continue; + } + + t = this.options.toleranceElement ? + $( this.options.toleranceElement, item.item ) : + item.item; + + if ( !fast ) { + item.width = t.outerWidth(); + item.height = t.outerHeight(); + } + + p = t.offset(); + item.left = p.left; + item.top = p.top; + } + }, + + refreshPositions: function( fast ) { + + // Determine whether items are being displayed horizontally + this.floating = this.items.length ? + this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : + false; + + if ( this.innermostContainer !== null ) { + this._refreshItemPositions( fast ); + } + + var i, p; + + if ( this.options.custom && this.options.custom.refreshContainers ) { + this.options.custom.refreshContainers.call( this ); + } else { + for ( i = this.containers.length - 1; i >= 0; i-- ) { + p = this.containers[ i ].element.offset(); + this.containers[ i ].containerCache.left = p.left; + this.containers[ i ].containerCache.top = p.top; + this.containers[ i ].containerCache.width = + this.containers[ i ].element.outerWidth(); + this.containers[ i ].containerCache.height = + this.containers[ i ].element.outerHeight(); + } + } + + return this; + }, + + _createPlaceholder: function( that ) { + that = that || this; + var className, nodeName, + o = that.options; + + if ( !o.placeholder || o.placeholder.constructor === String ) { + className = o.placeholder; + nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(); + o.placeholder = { + element: function() { + + var element = $( "<" + nodeName + ">", that.document[ 0 ] ); + + that._addClass( element, "ui-sortable-placeholder", + className || that.currentItem[ 0 ].className ) + ._removeClass( element, "ui-sortable-helper" ); + + if ( nodeName === "tbody" ) { + that._createTrPlaceholder( + that.currentItem.find( "tr" ).eq( 0 ), + $( "<tr>", that.document[ 0 ] ).appendTo( element ) + ); + } else if ( nodeName === "tr" ) { + that._createTrPlaceholder( that.currentItem, element ); + } else if ( nodeName === "img" ) { + element.attr( "src", that.currentItem.attr( "src" ) ); + } + + if ( !className ) { + element.css( "visibility", "hidden" ); + } + + return element; + }, + update: function( container, p ) { + + // 1. If a className is set as 'placeholder option, we don't force sizes - + // the class is responsible for that + // 2. The option 'forcePlaceholderSize can be enabled to force it even if a + // class name is specified + if ( className && !o.forcePlaceholderSize ) { + return; + } + + // If the element doesn't have a actual height or width by itself (without + // styles coming from a stylesheet), it receives the inline height and width + // from the dragged item. Or, if it's a tbody or tr, it's going to have a height + // anyway since we're populating them with <td>s above, but they're unlikely to + // be the correct height on their own if the row heights are dynamic, so we'll + // always assign the height of the dragged item given forcePlaceholderSize + // is true. + if ( !p.height() || ( o.forcePlaceholderSize && + ( nodeName === "tbody" || nodeName === "tr" ) ) ) { + p.height( + that.currentItem.innerHeight() - + parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) - + parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) ); + } + if ( !p.width() ) { + p.width( + that.currentItem.innerWidth() - + parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) - + parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) ); + } + } + }; + } + + //Create the placeholder + that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) ); + + //Append it after the actual current item + that.currentItem.after( that.placeholder ); + + //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) + o.placeholder.update( that, that.placeholder ); + + }, + + _createTrPlaceholder: function( sourceTr, targetTr ) { + var that = this; + + sourceTr.children().each( function() { + $( "<td> </td>", that.document[ 0 ] ) + .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) + .appendTo( targetTr ); + } ); + }, + + _contactContainers: function( event ) { + var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, + floating, axis, + innermostContainer = null, + innermostIndex = null; + + // Get innermost container that intersects with item + for ( i = this.containers.length - 1; i >= 0; i-- ) { + + // Never consider a container that's located within the item itself + if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) { + continue; + } + + if ( this._intersectsWith( this.containers[ i ].containerCache ) ) { + + // If we've already found a container and it's more "inner" than this, then continue + if ( innermostContainer && + $.contains( + this.containers[ i ].element[ 0 ], + innermostContainer.element[ 0 ] ) ) { + continue; + } + + innermostContainer = this.containers[ i ]; + innermostIndex = i; + + } else { + + // container doesn't intersect. trigger "out" event if necessary + if ( this.containers[ i ].containerCache.over ) { + this.containers[ i ]._trigger( "out", event, this._uiHash( this ) ); + this.containers[ i ].containerCache.over = 0; + } + } + + } + + this.innermostContainer = innermostContainer; + + // If no intersecting containers found, return + if ( !innermostContainer ) { + return; + } + + // Move the item into the container if it's not there already + if ( this.containers.length === 1 ) { + if ( !this.containers[ innermostIndex ].containerCache.over ) { + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); + this.containers[ innermostIndex ].containerCache.over = 1; + } + } else { + + // When entering a new container, we will find the item with the least distance and + // append our item near it + dist = 10000; + itemWithLeastDistance = null; + floating = innermostContainer.floating || this._isFloating( this.currentItem ); + posProperty = floating ? "left" : "top"; + sizeProperty = floating ? "width" : "height"; + axis = floating ? "pageX" : "pageY"; + + for ( j = this.items.length - 1; j >= 0; j-- ) { + if ( !$.contains( + this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] ) + ) { + continue; + } + if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) { + continue; + } + + cur = this.items[ j ].item.offset()[ posProperty ]; + nearBottom = false; + if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) { + nearBottom = true; + } + + if ( Math.abs( event[ axis ] - cur ) < dist ) { + dist = Math.abs( event[ axis ] - cur ); + itemWithLeastDistance = this.items[ j ]; + this.direction = nearBottom ? "up" : "down"; + } + } + + //Check if dropOnEmpty is enabled + if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) { + return; + } + + if ( this.currentContainer === this.containers[ innermostIndex ] ) { + if ( !this.currentContainer.containerCache.over ) { + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() ); + this.currentContainer.containerCache.over = 1; + } + return; + } + + if ( itemWithLeastDistance ) { + this._rearrange( event, itemWithLeastDistance, null, true ); + } else { + this._rearrange( event, null, this.containers[ innermostIndex ].element, true ); + } + this._trigger( "change", event, this._uiHash() ); + this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) ); + this.currentContainer = this.containers[ innermostIndex ]; + + //Update the placeholder + this.options.placeholder.update( this.currentContainer, this.placeholder ); + + //Update scrollParent + this.scrollParent = this.placeholder.scrollParent(); + + //Update overflowOffset + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + this.overflowOffset = this.scrollParent.offset(); + } + + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); + this.containers[ innermostIndex ].containerCache.over = 1; + } + + }, + + _createHelper: function( event ) { + + var o = this.options, + helper = typeof o.helper === "function" ? + $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) : + ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem ); + + //Add the helper to the DOM if that didn't happen already + if ( !helper.parents( "body" ).length ) { + this.appendTo[ 0 ].appendChild( helper[ 0 ] ); + } + + if ( helper[ 0 ] === this.currentItem[ 0 ] ) { + this._storedCSS = { + width: this.currentItem[ 0 ].style.width, + height: this.currentItem[ 0 ].style.height, + position: this.currentItem.css( "position" ), + top: this.currentItem.css( "top" ), + left: this.currentItem.css( "left" ) + }; + } + + if ( !helper[ 0 ].style.width || o.forceHelperSize ) { + helper.width( this.currentItem.width() ); + } + if ( !helper[ 0 ].style.height || o.forceHelperSize ) { + helper.height( this.currentItem.height() ); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function( obj ) { + if ( typeof obj === "string" ) { + obj = obj.split( " " ); + } + if ( Array.isArray( obj ) ) { + obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; + } + if ( "left" in obj ) { + this.offset.click.left = obj.left + this.margins.left; + } + if ( "right" in obj ) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ( "top" in obj ) { + this.offset.click.top = obj.top + this.margins.top; + } + if ( "bottom" in obj ) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the + // following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the + // next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't + // the document, which means that the scroll is included in the initial calculation of the + // offset of the parent, and never recalculated upon drag + if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] && + $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + // This needs to be actually done for all browsers, since pageX/pageY includes this + // information with an ugly IE fix + if ( this.offsetParent[ 0 ] === this.document[ 0 ].body || + ( this.offsetParent[ 0 ].tagName && + this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), + left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) + }; + + }, + + _getRelativeOffset: function() { + + if ( this.cssPosition === "relative" ) { + var p = this.currentItem.position(); + return { + top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + + this.scrollParent.scrollTop(), + left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ), + top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 ) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var ce, co, over, + o = this.options; + if ( o.containment === "parent" ) { + o.containment = this.helper[ 0 ].parentNode; + } + if ( o.containment === "document" || o.containment === "window" ) { + this.containment = [ + 0 - this.offset.relative.left - this.offset.parent.left, + 0 - this.offset.relative.top - this.offset.parent.top, + o.containment === "document" ? + this.document.width() : + this.window.width() - this.helperProportions.width - this.margins.left, + ( o.containment === "document" ? + ( this.document.height() || document.body.parentNode.scrollHeight ) : + this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight + ) - this.helperProportions.height - this.margins.top + ]; + } + + if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) { + ce = $( o.containment )[ 0 ]; + co = $( o.containment ).offset(); + over = ( $( ce ).css( "overflow" ) !== "hidden" ); + + this.containment = [ + co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) + + ( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left, + co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) + + ( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top, + co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) - + ( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) - + this.helperProportions.width - this.margins.left, + co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) - + ( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) - + this.helperProportions.height - this.margins.top + ]; + } + + }, + + _convertPositionTo: function( d, pos ) { + + if ( !pos ) { + pos = this.position; + } + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && + !( this.scrollParent[ 0 ] !== this.document[ 0 ] && + $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? + this.offsetParent : + this.scrollParent, + scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); + + return { + top: ( + + // The absolute mouse position + pos.top + + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.top * mod + + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.top * mod - + ( ( this.cssPosition === "fixed" ? + -this.scrollParent.scrollTop() : + ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod ) + ), + left: ( + + // The absolute mouse position + pos.left + + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.left * mod + + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.left * mod - + ( ( this.cssPosition === "fixed" ? + -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : + scroll.scrollLeft() ) * mod ) + ) + }; + + }, + + _generatePosition: function( event ) { + + var top, left, + o = this.options, + pageX = event.pageX, + pageY = event.pageY, + scroll = this.cssPosition === "absolute" && + !( this.scrollParent[ 0 ] !== this.document[ 0 ] && + $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? + this.offsetParent : + this.scrollParent, + scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); + + // This is another very weird special case that only happens for relative elements: + // 1. If the css position is relative + // 2. and the scroll parent is the document or similar to the offset parent + // we have to refresh the relative offset during the scroll so there are no jumps + if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) { + this.offset.relative = this._getRelativeOffset(); + } + + /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ - if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options - - if ( this.containment ) { - if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) { - pageX = this.containment[ 0 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) { - pageY = this.containment[ 1 ] + this.offset.click.top; - } - if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) { - pageX = this.containment[ 2 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) { - pageY = this.containment[ 3 ] + this.offset.click.top; - } - } - - if ( o.grid ) { - top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) / - o.grid[ 1 ] ) * o.grid[ 1 ]; - pageY = this.containment ? - ( ( top - this.offset.click.top >= this.containment[ 1 ] && - top - this.offset.click.top <= this.containment[ 3 ] ) ? - top : - ( ( top - this.offset.click.top >= this.containment[ 1 ] ) ? - top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : - top; - - left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) / - o.grid[ 0 ] ) * o.grid[ 0 ]; - pageX = this.containment ? - ( ( left - this.offset.click.left >= this.containment[ 0 ] && - left - this.offset.click.left <= this.containment[ 2 ] ) ? - left : - ( ( left - this.offset.click.left >= this.containment[ 0 ] ) ? - left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : - left; - } - - } - - return { - top: ( - - // The absolute mouse position - pageY - - - // Click offset (relative to the element) - this.offset.click.top - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top + - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollTop() : - ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) ) - ), - left: ( - - // The absolute mouse position - pageX - - - // Click offset (relative to the element) - this.offset.click.left - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left + - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollLeft() : - scrollIsRootNode ? 0 : scroll.scrollLeft() ) ) - ) - }; - - }, - - _rearrange: function( event, i, a, hardRefresh ) { - - a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) : - i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ], - ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) ); - - //Various things done here to improve the performance: - // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append - // 3. on the local scope, we copy the counter variable, and check in the timeout, - // if it's still the same - // 4. this lets only the last addition to the timeout stack through - this.counter = this.counter ? ++this.counter : 1; - var counter = this.counter; - - this._delay( function() { - if ( counter === this.counter ) { - - //Precompute after each DOM insertion, NOT on mousemove - this.refreshPositions( !hardRefresh ); - } - } ); - - }, - - _clear: function( event, noPropagation ) { - - this.reverting = false; - - // We delay all events that have to be triggered to after the point where the placeholder - // has been removed and everything else normalized again - var i, - delayedTriggers = []; - - // We first have to update the dom position of the actual currentItem - // Note: don't do it if the current item is already removed (by a user), or it gets - // reappended (see #4088) - if ( !this._noFinalSort && this.currentItem.parent().length ) { - this.placeholder.before( this.currentItem ); - } - this._noFinalSort = null; - - if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) { - for ( i in this._storedCSS ) { - if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) { - this._storedCSS[ i ] = ""; - } - } - this.currentItem.css( this._storedCSS ); - this._removeClass( this.currentItem, "ui-sortable-helper" ); - } else { - this.currentItem.show(); - } - - if ( this.fromOutside && !noPropagation ) { - delayedTriggers.push( function( event ) { - this._trigger( "receive", event, this._uiHash( this.fromOutside ) ); - } ); - } - if ( ( this.fromOutside || - this.domPosition.prev !== - this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] || - this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) { - - // Trigger update callback if the DOM position has changed - delayedTriggers.push( function( event ) { - this._trigger( "update", event, this._uiHash() ); - } ); - } - - // Check if the items Container has Changed and trigger appropriate - // events. - if ( this !== this.currentContainer ) { - if ( !noPropagation ) { - delayedTriggers.push( function( event ) { - this._trigger( "remove", event, this._uiHash() ); - } ); - delayedTriggers.push( ( function( c ) { - return function( event ) { - c._trigger( "receive", event, this._uiHash( this ) ); - }; - } ).call( this, this.currentContainer ) ); - delayedTriggers.push( ( function( c ) { - return function( event ) { - c._trigger( "update", event, this._uiHash( this ) ); - }; - } ).call( this, this.currentContainer ) ); - } - } - - //Post events to containers - function delayEvent( type, instance, container ) { - return function( event ) { - container._trigger( type, event, instance._uiHash( instance ) ); - }; - } - for ( i = this.containers.length - 1; i >= 0; i-- ) { - if ( !noPropagation ) { - delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); - } - if ( this.containers[ i ].containerCache.over ) { - delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - //Do what was originally in plugins - if ( this.storedCursor ) { - this.document.find( "body" ).css( "cursor", this.storedCursor ); - this.storedStylesheet.remove(); - } - if ( this._storedOpacity ) { - this.helper.css( "opacity", this._storedOpacity ); - } - if ( this._storedZIndex ) { - this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex ); - } - - this.dragging = false; - - if ( !noPropagation ) { - this._trigger( "beforeStop", event, this._uiHash() ); - } - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, - // it unbinds ALL events from the original node! - this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); - - if ( !this.cancelHelperRemoval ) { - if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { - this.helper.remove(); - } - this.helper = null; - } - - if ( !noPropagation ) { - for ( i = 0; i < delayedTriggers.length; i++ ) { - - // Trigger all delayed events - delayedTriggers[ i ].call( this, event ); - } - this._trigger( "stop", event, this._uiHash() ); - } - - this.fromOutside = false; - return !this.cancelHelperRemoval; - - }, - - _trigger: function() { - if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) { - this.cancel(); - } - }, - - _uiHash: function( _inst ) { - var inst = _inst || this; - return { - helper: inst.helper, - placeholder: inst.placeholder || $( [] ), - position: inst.position, - originalPosition: inst.originalPosition, - offset: inst.positionAbs, - item: inst.currentItem, - sender: _inst ? _inst.element : null - }; - } - -} ); - - -/*! - * jQuery UI Accordion 1.12.1 + if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options + + if ( this.containment ) { + if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) { + pageX = this.containment[ 0 ] + this.offset.click.left; + } + if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) { + pageY = this.containment[ 1 ] + this.offset.click.top; + } + if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) { + pageX = this.containment[ 2 ] + this.offset.click.left; + } + if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) { + pageY = this.containment[ 3 ] + this.offset.click.top; + } + } + + if ( o.grid ) { + top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) / + o.grid[ 1 ] ) * o.grid[ 1 ]; + pageY = this.containment ? + ( ( top - this.offset.click.top >= this.containment[ 1 ] && + top - this.offset.click.top <= this.containment[ 3 ] ) ? + top : + ( ( top - this.offset.click.top >= this.containment[ 1 ] ) ? + top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : + top; + + left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) / + o.grid[ 0 ] ) * o.grid[ 0 ]; + pageX = this.containment ? + ( ( left - this.offset.click.left >= this.containment[ 0 ] && + left - this.offset.click.left <= this.containment[ 2 ] ) ? + left : + ( ( left - this.offset.click.left >= this.containment[ 0 ] ) ? + left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : + left; + } + + } + + return { + top: ( + + // The absolute mouse position + pageY - + + // Click offset (relative to the element) + this.offset.click.top - + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.top - + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.top + + ( ( this.cssPosition === "fixed" ? + -this.scrollParent.scrollTop() : + ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) ) + ), + left: ( + + // The absolute mouse position + pageX - + + // Click offset (relative to the element) + this.offset.click.left - + + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.relative.left - + + // The offsetParent's offset without borders (offset + border) + this.offset.parent.left + + ( ( this.cssPosition === "fixed" ? + -this.scrollParent.scrollLeft() : + scrollIsRootNode ? 0 : scroll.scrollLeft() ) ) + ) + }; + + }, + + _rearrange: function( event, i, a, hardRefresh ) { + + if ( a ) { + a[ 0 ].appendChild( this.placeholder[ 0 ] ); + } else { + i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ], + ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) ); + } + + //Various things done here to improve the performance: + // 1. we create a setTimeout, that calls refreshPositions + // 2. on the instance, we have a counter variable, that get's higher after every append + // 3. on the local scope, we copy the counter variable, and check in the timeout, + // if it's still the same + // 4. this lets only the last addition to the timeout stack through + this.counter = this.counter ? ++this.counter : 1; + var counter = this.counter; + + this._delay( function() { + if ( counter === this.counter ) { + + //Precompute after each DOM insertion, NOT on mousemove + this.refreshPositions( !hardRefresh ); + } + } ); + + }, + + _clear: function( event, noPropagation ) { + + this.reverting = false; + + // We delay all events that have to be triggered to after the point where the placeholder + // has been removed and everything else normalized again + var i, + delayedTriggers = []; + + // We first have to update the dom position of the actual currentItem + // Note: don't do it if the current item is already removed (by a user), or it gets + // reappended (see #4088) + if ( !this._noFinalSort && this.currentItem.parent().length ) { + this.placeholder.before( this.currentItem ); + } + this._noFinalSort = null; + + if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) { + for ( i in this._storedCSS ) { + if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) { + this._storedCSS[ i ] = ""; + } + } + this.currentItem.css( this._storedCSS ); + this._removeClass( this.currentItem, "ui-sortable-helper" ); + } else { + this.currentItem.show(); + } + + if ( this.fromOutside && !noPropagation ) { + delayedTriggers.push( function( event ) { + this._trigger( "receive", event, this._uiHash( this.fromOutside ) ); + } ); + } + if ( ( this.fromOutside || + this.domPosition.prev !== + this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] || + this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) { + + // Trigger update callback if the DOM position has changed + delayedTriggers.push( function( event ) { + this._trigger( "update", event, this._uiHash() ); + } ); + } + + // Check if the items Container has Changed and trigger appropriate + // events. + if ( this !== this.currentContainer ) { + if ( !noPropagation ) { + delayedTriggers.push( function( event ) { + this._trigger( "remove", event, this._uiHash() ); + } ); + delayedTriggers.push( ( function( c ) { + return function( event ) { + c._trigger( "receive", event, this._uiHash( this ) ); + }; + } ).call( this, this.currentContainer ) ); + delayedTriggers.push( ( function( c ) { + return function( event ) { + c._trigger( "update", event, this._uiHash( this ) ); + }; + } ).call( this, this.currentContainer ) ); + } + } + + //Post events to containers + function delayEvent( type, instance, container ) { + return function( event ) { + container._trigger( type, event, instance._uiHash( instance ) ); + }; + } + for ( i = this.containers.length - 1; i >= 0; i-- ) { + if ( !noPropagation ) { + delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); + } + if ( this.containers[ i ].containerCache.over ) { + delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); + this.containers[ i ].containerCache.over = 0; + } + } + + //Do what was originally in plugins + if ( this.storedCursor ) { + this.document.find( "body" ).css( "cursor", this.storedCursor ); + this.storedStylesheet.remove(); + } + if ( this._storedOpacity ) { + this.helper.css( "opacity", this._storedOpacity ); + } + if ( this._storedZIndex ) { + this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex ); + } + + this.dragging = false; + + if ( !noPropagation ) { + this._trigger( "beforeStop", event, this._uiHash() ); + } + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, + // it unbinds ALL events from the original node! + this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); + + if ( !this.cancelHelperRemoval ) { + if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { + this.helper.remove(); + } + this.helper = null; + } + + if ( !noPropagation ) { + for ( i = 0; i < delayedTriggers.length; i++ ) { + + // Trigger all delayed events + delayedTriggers[ i ].call( this, event ); + } + this._trigger( "stop", event, this._uiHash() ); + } + + this.fromOutside = false; + return !this.cancelHelperRemoval; + + }, + + _trigger: function() { + if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) { + this.cancel(); + } + }, + + _uiHash: function( _inst ) { + var inst = _inst || this; + return { + helper: inst.helper, + placeholder: inst.placeholder || $( [] ), + position: inst.position, + originalPosition: inst.originalPosition, + offset: inst.positionAbs, + item: inst.currentItem, + sender: _inst ? _inst.element : null + }; + } + + } ); + + + /*! + * jQuery UI Accordion 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -6747,9 +6842,9 @@ var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { //>>label: Accordion //>>group: Widgets -// jscs:disable maximumLineLength + /* eslint-disable max-len */ //>>description: Displays collapsible content panels for presenting information in a limited amount of space. -// jscs:enable maximumLineLength + /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/accordion/ //>>demos: http://jqueryui.com/accordion/ //>>css.structure: ../../themes/base/core.css @@ -6757,584 +6852,589 @@ var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { //>>css.theme: ../../themes/base/theme.css - -var widgetsAccordion = $.widget( "ui.accordion", { - version: "1.12.1", - options: { - active: 0, - animate: {}, - classes: { - "ui-accordion-header": "ui-corner-top", - "ui-accordion-header-collapsed": "ui-corner-all", - "ui-accordion-content": "ui-corner-bottom" - }, - collapsible: false, - event: "click", - header: "> li > :first-child, > :not(li):even", - heightStyle: "auto", - icons: { - activeHeader: "ui-icon-triangle-1-s", - header: "ui-icon-triangle-1-e" - }, - - // Callbacks - activate: null, - beforeActivate: null - }, - - hideProps: { - borderTopWidth: "hide", - borderBottomWidth: "hide", - paddingTop: "hide", - paddingBottom: "hide", - height: "hide" - }, - - showProps: { - borderTopWidth: "show", - borderBottomWidth: "show", - paddingTop: "show", - paddingBottom: "show", - height: "show" - }, - - _create: function() { - var options = this.options; - - this.prevShow = this.prevHide = $(); - this._addClass( "ui-accordion", "ui-widget ui-helper-reset" ); - this.element.attr( "role", "tablist" ); - - // Don't allow collapsible: false and active: false / null - if ( !options.collapsible && ( options.active === false || options.active == null ) ) { - options.active = 0; - } - - this._processPanels(); - - // handle negative values - if ( options.active < 0 ) { - options.active += this.headers.length; - } - this._refresh(); - }, - - _getCreateEventData: function() { - return { - header: this.active, - panel: !this.active.length ? $() : this.active.next() - }; - }, - - _createIcons: function() { - var icon, children, - icons = this.options.icons; - - if ( icons ) { - icon = $( "<span>" ); - this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header ); - icon.prependTo( this.headers ); - children = this.active.children( ".ui-accordion-header-icon" ); - this._removeClass( children, icons.header ) - ._addClass( children, null, icons.activeHeader ) - ._addClass( this.headers, "ui-accordion-icons" ); - } - }, - - _destroyIcons: function() { - this._removeClass( this.headers, "ui-accordion-icons" ); - this.headers.children( ".ui-accordion-header-icon" ).remove(); - }, - - _destroy: function() { - var contents; - - // Clean up main element - this.element.removeAttr( "role" ); - - // Clean up headers - this.headers - .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" ) - .removeUniqueId(); - - this._destroyIcons(); - - // Clean up content panels - contents = this.headers.next() - .css( "display", "" ) - .removeAttr( "role aria-hidden aria-labelledby" ) - .removeUniqueId(); - - if ( this.options.heightStyle !== "content" ) { - contents.css( "height", "" ); - } - }, - - _setOption: function( key, value ) { - if ( key === "active" ) { - - // _activate() will handle invalid values and update this.options - this._activate( value ); - return; - } - - if ( key === "event" ) { - if ( this.options.event ) { - this._off( this.headers, this.options.event ); - } - this._setupEvents( value ); - } - - this._super( key, value ); - - // Setting collapsible: false while collapsed; open first panel - if ( key === "collapsible" && !value && this.options.active === false ) { - this._activate( 0 ); - } - - if ( key === "icons" ) { - this._destroyIcons(); - if ( value ) { - this._createIcons(); - } - } - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this.element.attr( "aria-disabled", value ); - - // Support: IE8 Only - // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE - // so we need to add the disabled class to the headers and panels - this._toggleClass( null, "ui-state-disabled", !!value ); - this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", - !!value ); - }, - - _keydown: function( event ) { - if ( event.altKey || event.ctrlKey ) { - return; - } - - var keyCode = $.ui.keyCode, - length = this.headers.length, - currentIndex = this.headers.index( event.target ), - toFocus = false; - - switch ( event.keyCode ) { - case keyCode.RIGHT: - case keyCode.DOWN: - toFocus = this.headers[ ( currentIndex + 1 ) % length ]; - break; - case keyCode.LEFT: - case keyCode.UP: - toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; - break; - case keyCode.SPACE: - case keyCode.ENTER: - this._eventHandler( event ); - break; - case keyCode.HOME: - toFocus = this.headers[ 0 ]; - break; - case keyCode.END: - toFocus = this.headers[ length - 1 ]; - break; - } - - if ( toFocus ) { - $( event.target ).attr( "tabIndex", -1 ); - $( toFocus ).attr( "tabIndex", 0 ); - $( toFocus ).trigger( "focus" ); - event.preventDefault(); - } - }, - - _panelKeyDown: function( event ) { - if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { - $( event.currentTarget ).prev().trigger( "focus" ); - } - }, - - refresh: function() { - var options = this.options; - this._processPanels(); - - // Was collapsed or no panel - if ( ( options.active === false && options.collapsible === true ) || - !this.headers.length ) { - options.active = false; - this.active = $(); - - // active false only when collapsible is true - } else if ( options.active === false ) { - this._activate( 0 ); - - // was active, but active panel is gone - } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { - - // all remaining panel are disabled - if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) { - options.active = false; - this.active = $(); - - // activate previous panel - } else { - this._activate( Math.max( 0, options.active - 1 ) ); - } - - // was active, active panel still exists - } else { - - // make sure active index is correct - options.active = this.headers.index( this.active ); - } - - this._destroyIcons(); - - this._refresh(); - }, - - _processPanels: function() { - var prevHeaders = this.headers, - prevPanels = this.panels; - - this.headers = this.element.find( this.options.header ); - this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", - "ui-state-default" ); - - this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide(); - this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" ); - - // Avoid memory leaks (#10056) - if ( prevPanels ) { - this._off( prevHeaders.not( this.headers ) ); - this._off( prevPanels.not( this.panels ) ); - } - }, - - _refresh: function() { - var maxHeight, - options = this.options, - heightStyle = options.heightStyle, - parent = this.element.parent(); - - this.active = this._findActive( options.active ); - this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" ) - ._removeClass( this.active, "ui-accordion-header-collapsed" ); - this._addClass( this.active.next(), "ui-accordion-content-active" ); - this.active.next().show(); - - this.headers - .attr( "role", "tab" ) - .each( function() { - var header = $( this ), - headerId = header.uniqueId().attr( "id" ), - panel = header.next(), - panelId = panel.uniqueId().attr( "id" ); - header.attr( "aria-controls", panelId ); - panel.attr( "aria-labelledby", headerId ); - } ) - .next() - .attr( "role", "tabpanel" ); - - this.headers - .not( this.active ) - .attr( { - "aria-selected": "false", - "aria-expanded": "false", - tabIndex: -1 - } ) - .next() - .attr( { - "aria-hidden": "true" - } ) - .hide(); - - // Make sure at least one header is in the tab order - if ( !this.active.length ) { - this.headers.eq( 0 ).attr( "tabIndex", 0 ); - } else { - this.active.attr( { - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - } ) - .next() - .attr( { - "aria-hidden": "false" - } ); - } - - this._createIcons(); - - this._setupEvents( options.event ); - - if ( heightStyle === "fill" ) { - maxHeight = parent.height(); - this.element.siblings( ":visible" ).each( function() { - var elem = $( this ), - position = elem.css( "position" ); - - if ( position === "absolute" || position === "fixed" ) { - return; - } - maxHeight -= elem.outerHeight( true ); - } ); - - this.headers.each( function() { - maxHeight -= $( this ).outerHeight( true ); - } ); - - this.headers.next() - .each( function() { - $( this ).height( Math.max( 0, maxHeight - - $( this ).innerHeight() + $( this ).height() ) ); - } ) - .css( "overflow", "auto" ); - } else if ( heightStyle === "auto" ) { - maxHeight = 0; - this.headers.next() - .each( function() { - var isVisible = $( this ).is( ":visible" ); - if ( !isVisible ) { - $( this ).show(); - } - maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); - if ( !isVisible ) { - $( this ).hide(); - } - } ) - .height( maxHeight ); - } - }, - - _activate: function( index ) { - var active = this._findActive( index )[ 0 ]; - - // Trying to activate the already active panel - if ( active === this.active[ 0 ] ) { - return; - } - - // Trying to collapse, simulate a click on the currently active header - active = active || this.active[ 0 ]; - - this._eventHandler( { - target: active, - currentTarget: active, - preventDefault: $.noop - } ); - }, - - _findActive: function( selector ) { - return typeof selector === "number" ? this.headers.eq( selector ) : $(); - }, - - _setupEvents: function( event ) { - var events = { - keydown: "_keydown" - }; - if ( event ) { - $.each( event.split( " " ), function( index, eventName ) { - events[ eventName ] = "_eventHandler"; - } ); - } - - this._off( this.headers.add( this.headers.next() ) ); - this._on( this.headers, events ); - this._on( this.headers.next(), { keydown: "_panelKeyDown" } ); - this._hoverable( this.headers ); - this._focusable( this.headers ); - }, - - _eventHandler: function( event ) { - var activeChildren, clickedChildren, - options = this.options, - active = this.active, - clicked = $( event.currentTarget ), - clickedIsActive = clicked[ 0 ] === active[ 0 ], - collapsing = clickedIsActive && options.collapsible, - toShow = collapsing ? $() : clicked.next(), - toHide = active.next(), - eventData = { - oldHeader: active, - oldPanel: toHide, - newHeader: collapsing ? $() : clicked, - newPanel: toShow - }; - - event.preventDefault(); - - if ( - - // click on active header, but not collapsible - ( clickedIsActive && !options.collapsible ) || - - // allow canceling activation - ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { - return; - } - - options.active = collapsing ? false : this.headers.index( clicked ); - - // When the call to ._toggle() comes after the class changes - // it causes a very odd bug in IE 8 (see #6720) - this.active = clickedIsActive ? $() : clicked; - this._toggle( eventData ); - - // Switch classes - // corner classes on the previously active header stay after the animation - this._removeClass( active, "ui-accordion-header-active", "ui-state-active" ); - if ( options.icons ) { - activeChildren = active.children( ".ui-accordion-header-icon" ); - this._removeClass( activeChildren, null, options.icons.activeHeader ) - ._addClass( activeChildren, null, options.icons.header ); - } - - if ( !clickedIsActive ) { - this._removeClass( clicked, "ui-accordion-header-collapsed" ) - ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" ); - if ( options.icons ) { - clickedChildren = clicked.children( ".ui-accordion-header-icon" ); - this._removeClass( clickedChildren, null, options.icons.header ) - ._addClass( clickedChildren, null, options.icons.activeHeader ); - } - - this._addClass( clicked.next(), "ui-accordion-content-active" ); - } - }, - - _toggle: function( data ) { - var toShow = data.newPanel, - toHide = this.prevShow.length ? this.prevShow : data.oldPanel; - - // Handle activating a panel during the animation for another activation - this.prevShow.add( this.prevHide ).stop( true, true ); - this.prevShow = toShow; - this.prevHide = toHide; - - if ( this.options.animate ) { - this._animate( toShow, toHide, data ); - } else { - toHide.hide(); - toShow.show(); - this._toggleComplete( data ); - } - - toHide.attr( { - "aria-hidden": "true" - } ); - toHide.prev().attr( { - "aria-selected": "false", - "aria-expanded": "false" - } ); - - // if we're switching panels, remove the old header from the tab order - // if we're opening from collapsed state, remove the previous header from the tab order - // if we're collapsing, then keep the collapsing header in the tab order - if ( toShow.length && toHide.length ) { - toHide.prev().attr( { - "tabIndex": -1, - "aria-expanded": "false" - } ); - } else if ( toShow.length ) { - this.headers.filter( function() { - return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; - } ) - .attr( "tabIndex", -1 ); - } - - toShow - .attr( "aria-hidden", "false" ) - .prev() - .attr( { - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - } ); - }, - - _animate: function( toShow, toHide, data ) { - var total, easing, duration, - that = this, - adjust = 0, - boxSizing = toShow.css( "box-sizing" ), - down = toShow.length && - ( !toHide.length || ( toShow.index() < toHide.index() ) ), - animate = this.options.animate || {}, - options = down && animate.down || animate, - complete = function() { - that._toggleComplete( data ); - }; - - if ( typeof options === "number" ) { - duration = options; - } - if ( typeof options === "string" ) { - easing = options; - } - - // fall back from options to animation in case of partial down settings - easing = easing || options.easing || animate.easing; - duration = duration || options.duration || animate.duration; - - if ( !toHide.length ) { - return toShow.animate( this.showProps, duration, easing, complete ); - } - if ( !toShow.length ) { - return toHide.animate( this.hideProps, duration, easing, complete ); - } - - total = toShow.show().outerHeight(); - toHide.animate( this.hideProps, { - duration: duration, - easing: easing, - step: function( now, fx ) { - fx.now = Math.round( now ); - } - } ); - toShow - .hide() - .animate( this.showProps, { - duration: duration, - easing: easing, - complete: complete, - step: function( now, fx ) { - fx.now = Math.round( now ); - if ( fx.prop !== "height" ) { - if ( boxSizing === "content-box" ) { - adjust += fx.now; - } - } else if ( that.options.heightStyle !== "content" ) { - fx.now = Math.round( total - toHide.outerHeight() - adjust ); - adjust = 0; - } - } - } ); - }, - - _toggleComplete: function( data ) { - var toHide = data.oldPanel, - prev = toHide.prev(); - - this._removeClass( toHide, "ui-accordion-content-active" ); - this._removeClass( prev, "ui-accordion-header-active" ) - ._addClass( prev, "ui-accordion-header-collapsed" ); - - // Work around for rendering bug in IE (#5421) - if ( toHide.length ) { - toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; - } - this._trigger( "activate", null, data ); - } -} ); - - -/*! - * jQuery UI Menu 1.12.1 + var widgetsAccordion = $.widget( "ui.accordion", { + version: "1.13.0", + options: { + active: 0, + animate: {}, + classes: { + "ui-accordion-header": "ui-corner-top", + "ui-accordion-header-collapsed": "ui-corner-all", + "ui-accordion-content": "ui-corner-bottom" + }, + collapsible: false, + event: "click", + header: function( elem ) { + return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() ); + }, + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // Callbacks + activate: null, + beforeActivate: null + }, + + hideProps: { + borderTopWidth: "hide", + borderBottomWidth: "hide", + paddingTop: "hide", + paddingBottom: "hide", + height: "hide" + }, + + showProps: { + borderTopWidth: "show", + borderBottomWidth: "show", + paddingTop: "show", + paddingBottom: "show", + height: "show" + }, + + _create: function() { + var options = this.options; + + this.prevShow = this.prevHide = $(); + this._addClass( "ui-accordion", "ui-widget ui-helper-reset" ); + this.element.attr( "role", "tablist" ); + + // Don't allow collapsible: false and active: false / null + if ( !options.collapsible && ( options.active === false || options.active == null ) ) { + options.active = 0; + } + + this._processPanels(); + + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icon, children, + icons = this.options.icons; + + if ( icons ) { + icon = $( "<span>" ); + this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header ); + icon.prependTo( this.headers ); + children = this.active.children( ".ui-accordion-header-icon" ); + this._removeClass( children, icons.header ) + ._addClass( children, null, icons.activeHeader ) + ._addClass( this.headers, "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this._removeClass( this.headers, "ui-accordion-icons" ); + this.headers.children( ".ui-accordion-header-icon" ).remove(); + }, + + _destroy: function() { + var contents; + + // Clean up main element + this.element.removeAttr( "role" ); + + // Clean up headers + this.headers + .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" ) + .removeUniqueId(); + + this._destroyIcons(); + + // Clean up content panels + contents = this.headers.next() + .css( "display", "" ) + .removeAttr( "role aria-hidden aria-labelledby" ) + .removeUniqueId(); + + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // Setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this.element.attr( "aria-disabled", value ); + + // Support: IE8 Only + // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + this._toggleClass( null, "ui-state-disabled", !!value ); + this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", + !!value ); + }, + + _keydown: function( event ) { + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + $( toFocus ).trigger( "focus" ); + event.preventDefault(); + } + }, + + _panelKeyDown: function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().trigger( "focus" ); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // Was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || + !this.headers.length ) { + options.active = false; + this.active = $(); + + // active false only when collapsible is true + } else if ( options.active === false ) { + this._activate( 0 ); + + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + + // all remaining panel are disabled + if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) { + options.active = false; + this.active = $(); + + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + + // was active, active panel still exists + } else { + + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + var prevHeaders = this.headers, + prevPanels = this.panels; + + if ( typeof this.options.header === "function" ) { + this.headers = this.options.header( this.element ); + } else { + this.headers = this.element.find( this.options.header ); + } + this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", + "ui-state-default" ); + + this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide(); + this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" ); + + // Avoid memory leaks (#10056) + if ( prevPanels ) { + this._off( prevHeaders.not( this.headers ) ); + this._off( prevPanels.not( this.panels ) ); + } + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(); + + this.active = this._findActive( options.active ); + this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" ) + ._removeClass( this.active, "ui-accordion-header-collapsed" ); + this._addClass( this.active.next(), "ui-accordion-content-active" ); + this.active.next().show(); + + this.headers + .attr( "role", "tab" ) + .each( function() { + var header = $( this ), + headerId = header.uniqueId().attr( "id" ), + panel = header.next(), + panelId = panel.uniqueId().attr( "id" ); + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + } ) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr( { + "aria-selected": "false", + "aria-expanded": "false", + tabIndex: -1 + } ) + .next() + .attr( { + "aria-hidden": "true" + } ) + .hide(); + + // Make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr( { + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + } ) + .next() + .attr( { + "aria-hidden": "false" + } ); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each( function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + } ); + + this.headers.each( function() { + maxHeight -= $( this ).outerHeight( true ); + } ); + + this.headers.next() + .each( function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + } ) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each( function() { + var isVisible = $( this ).is( ":visible" ); + if ( !isVisible ) { + $( this ).show(); + } + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + if ( !isVisible ) { + $( this ).hide(); + } + } ) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // Trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // Trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler( { + target: active, + currentTarget: active, + preventDefault: $.noop + } ); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split( " " ), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + } ); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" } ); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var activeChildren, clickedChildren, + options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // When the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // Switch classes + // corner classes on the previously active header stay after the animation + this._removeClass( active, "ui-accordion-header-active", "ui-state-active" ); + if ( options.icons ) { + activeChildren = active.children( ".ui-accordion-header-icon" ); + this._removeClass( activeChildren, null, options.icons.activeHeader ) + ._addClass( activeChildren, null, options.icons.header ); + } + + if ( !clickedIsActive ) { + this._removeClass( clicked, "ui-accordion-header-collapsed" ) + ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" ); + if ( options.icons ) { + clickedChildren = clicked.children( ".ui-accordion-header-icon" ); + this._removeClass( clickedChildren, null, options.icons.header ) + ._addClass( clickedChildren, null, options.icons.activeHeader ); + } + + this._addClass( clicked.next(), "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // Handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr( { + "aria-hidden": "true" + } ); + toHide.prev().attr( { + "aria-selected": "false", + "aria-expanded": "false" + } ); + + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr( { + "tabIndex": -1, + "aria-expanded": "false" + } ); + } else if ( toShow.length ) { + this.headers.filter( function() { + return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; + } ) + .attr( "tabIndex", -1 ); + } + + toShow + .attr( "aria-hidden", "false" ) + .prev() + .attr( { + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + } ); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + boxSizing = toShow.css( "box-sizing" ), + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( this.showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( this.hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( this.hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + } ); + toShow + .hide() + .animate( this.showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + if ( boxSizing === "content-box" ) { + adjust += fx.now; + } + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + } ); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel, + prev = toHide.prev(); + + this._removeClass( toHide, "ui-accordion-content-active" ); + this._removeClass( prev, "ui-accordion-header-active" ) + ._addClass( prev, "ui-accordion-header-collapsed" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; + } + this._trigger( "activate", null, data ); + } + } ); + + + /*! + * jQuery UI Menu 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -7352,644 +7452,680 @@ var widgetsAccordion = $.widget( "ui.accordion", { //>>css.theme: ../../themes/base/theme.css - -var widgetsMenu = $.widget( "ui.menu", { - version: "1.12.1", - defaultElement: "<ul>", - delay: 300, - options: { - icons: { - submenu: "ui-icon-caret-1-e" - }, - items: "> *", - menus: "ul", - position: { - my: "left top", - at: "right top" - }, - role: "menu", - - // Callbacks - blur: null, - focus: null, - select: null - }, - - _create: function() { - this.activeMenu = this.element; - - // Flag used to prevent firing of the click handler - // as the event bubbles up through nested menus - this.mouseHandled = false; - this.element - .uniqueId() - .attr( { - role: this.options.role, - tabIndex: 0 - } ); - - this._addClass( "ui-menu", "ui-widget ui-widget-content" ); - this._on( { - - // Prevent focus from sticking to links inside menu after clicking - // them (focus should always stay on UL during navigation). - "mousedown .ui-menu-item": function( event ) { - event.preventDefault(); - }, - "click .ui-menu-item": function( event ) { - var target = $( event.target ); - var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); - if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { - this.select( event ); - - // Only set the mouseHandled flag if the event will bubble, see #9469. - if ( !event.isPropagationStopped() ) { - this.mouseHandled = true; - } - - // Open submenu on click - if ( target.has( ".ui-menu" ).length ) { - this.expand( event ); - } else if ( !this.element.is( ":focus" ) && - active.closest( ".ui-menu" ).length ) { - - // Redirect focus to the menu - this.element.trigger( "focus", [ true ] ); - - // If the active item is on the top level, let it stay active. - // Otherwise, blur the active item since it is no longer visible. - if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { - clearTimeout( this.timer ); - } - } - } - }, - "mouseenter .ui-menu-item": function( event ) { - - // Ignore mouse events while typeahead is active, see #10458. - // Prevents focusing the wrong item when typeahead causes a scroll while the mouse - // is over an item in the menu - if ( this.previousFilter ) { - return; - } - - var actualTarget = $( event.target ).closest( ".ui-menu-item" ), - target = $( event.currentTarget ); - - // Ignore bubbled events on parent items, see #11641 - if ( actualTarget[ 0 ] !== target[ 0 ] ) { - return; - } - - // Remove ui-state-active class from siblings of the newly focused menu item - // to avoid a jump caused by adjacent elements both having a class with a border - this._removeClass( target.siblings().children( ".ui-state-active" ), - null, "ui-state-active" ); - this.focus( event, target ); - }, - mouseleave: "collapseAll", - "mouseleave .ui-menu": "collapseAll", - focus: function( event, keepActiveItem ) { - - // If there's already an active item, keep it active - // If not, activate the first item - var item = this.active || this.element.find( this.options.items ).eq( 0 ); - - if ( !keepActiveItem ) { - this.focus( event, item ); - } - }, - blur: function( event ) { - this._delay( function() { - var notContained = !$.contains( - this.element[ 0 ], - $.ui.safeActiveElement( this.document[ 0 ] ) - ); - if ( notContained ) { - this.collapseAll( event ); - } - } ); - }, - keydown: "_keydown" - } ); - - this.refresh(); - - // Clicks outside of a menu collapse any open menus - this._on( this.document, { - click: function( event ) { - if ( this._closeOnDocumentClick( event ) ) { - this.collapseAll( event ); - } - - // Reset the mouseHandled flag - this.mouseHandled = false; - } - } ); - }, - - _destroy: function() { - var items = this.element.find( ".ui-menu-item" ) - .removeAttr( "role aria-disabled" ), - submenus = items.children( ".ui-menu-item-wrapper" ) - .removeUniqueId() - .removeAttr( "tabIndex role aria-haspopup" ); - - // Destroy (sub)menus - this.element - .removeAttr( "aria-activedescendant" ) - .find( ".ui-menu" ).addBack() - .removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " + - "tabIndex" ) - .removeUniqueId() - .show(); - - submenus.children().each( function() { - var elem = $( this ); - if ( elem.data( "ui-menu-submenu-caret" ) ) { - elem.remove(); - } - } ); - }, - - _keydown: function( event ) { - var match, prev, character, skip, - preventDefault = true; - - switch ( event.keyCode ) { - case $.ui.keyCode.PAGE_UP: - this.previousPage( event ); - break; - case $.ui.keyCode.PAGE_DOWN: - this.nextPage( event ); - break; - case $.ui.keyCode.HOME: - this._move( "first", "first", event ); - break; - case $.ui.keyCode.END: - this._move( "last", "last", event ); - break; - case $.ui.keyCode.UP: - this.previous( event ); - break; - case $.ui.keyCode.DOWN: - this.next( event ); - break; - case $.ui.keyCode.LEFT: - this.collapse( event ); - break; - case $.ui.keyCode.RIGHT: - if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { - this.expand( event ); - } - break; - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - this._activate( event ); - break; - case $.ui.keyCode.ESCAPE: - this.collapse( event ); - break; - default: - preventDefault = false; - prev = this.previousFilter || ""; - skip = false; - - // Support number pad values - character = event.keyCode >= 96 && event.keyCode <= 105 ? - ( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode ); - - clearTimeout( this.filterTimer ); - - if ( character === prev ) { - skip = true; - } else { - character = prev + character; - } - - match = this._filterMenuItems( character ); - match = skip && match.index( this.active.next() ) !== -1 ? - this.active.nextAll( ".ui-menu-item" ) : - match; - - // If no matches on the current filter, reset to the last character pressed - // to move down the menu to the first item that starts with that character - if ( !match.length ) { - character = String.fromCharCode( event.keyCode ); - match = this._filterMenuItems( character ); - } - - if ( match.length ) { - this.focus( event, match ); - this.previousFilter = character; - this.filterTimer = this._delay( function() { - delete this.previousFilter; - }, 1000 ); - } else { - delete this.previousFilter; - } - } - - if ( preventDefault ) { - event.preventDefault(); - } - }, - - _activate: function( event ) { - if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { - if ( this.active.children( "[aria-haspopup='true']" ).length ) { - this.expand( event ); - } else { - this.select( event ); - } - } - }, - - refresh: function() { - var menus, items, newSubmenus, newItems, newWrappers, - that = this, - icon = this.options.icons.submenu, - submenus = this.element.find( this.options.menus ); - - this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length ); - - // Initialize nested menus - newSubmenus = submenus.filter( ":not(.ui-menu)" ) - .hide() - .attr( { - role: this.options.role, - "aria-hidden": "true", - "aria-expanded": "false" - } ) - .each( function() { - var menu = $( this ), - item = menu.prev(), - submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true ); - - that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon ); - item - .attr( "aria-haspopup", "true" ) - .prepend( submenuCaret ); - menu.attr( "aria-labelledby", item.attr( "id" ) ); - } ); - - this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" ); - - menus = submenus.add( this.element ); - items = menus.find( this.options.items ); - - // Initialize menu-items containing spaces and/or dashes only as dividers - items.not( ".ui-menu-item" ).each( function() { - var item = $( this ); - if ( that._isDivider( item ) ) { - that._addClass( item, "ui-menu-divider", "ui-widget-content" ); - } - } ); - - // Don't refresh list items that are already adapted - newItems = items.not( ".ui-menu-item, .ui-menu-divider" ); - newWrappers = newItems.children() - .not( ".ui-menu" ) - .uniqueId() - .attr( { - tabIndex: -1, - role: this._itemRole() - } ); - this._addClass( newItems, "ui-menu-item" ) - ._addClass( newWrappers, "ui-menu-item-wrapper" ); - - // Add aria-disabled attribute to any disabled menu item - items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); - - // If the active item has been removed, blur the menu - if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { - this.blur(); - } - }, - - _itemRole: function() { - return { - menu: "menuitem", - listbox: "option" - }[ this.options.role ]; - }, - - _setOption: function( key, value ) { - if ( key === "icons" ) { - var icons = this.element.find( ".ui-menu-icon" ); - this._removeClass( icons, null, this.options.icons.submenu ) - ._addClass( icons, null, value.submenu ); - } - this._super( key, value ); - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this.element.attr( "aria-disabled", String( value ) ); - this._toggleClass( null, "ui-state-disabled", !!value ); - }, - - focus: function( event, item ) { - var nested, focused, activeParent; - this.blur( event, event && event.type === "focus" ); - - this._scrollIntoView( item ); - - this.active = item.first(); - - focused = this.active.children( ".ui-menu-item-wrapper" ); - this._addClass( focused, null, "ui-state-active" ); - - // Only update aria-activedescendant if there's a role - // otherwise we assume focus is managed elsewhere - if ( this.options.role ) { - this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); - } - - // Highlight active parent menu item, if any - activeParent = this.active - .parent() - .closest( ".ui-menu-item" ) - .children( ".ui-menu-item-wrapper" ); - this._addClass( activeParent, null, "ui-state-active" ); - - if ( event && event.type === "keydown" ) { - this._close(); - } else { - this.timer = this._delay( function() { - this._close(); - }, this.delay ); - } - - nested = item.children( ".ui-menu" ); - if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { - this._startOpening( nested ); - } - this.activeMenu = item.parent(); - - this._trigger( "focus", event, { item: item } ); - }, - - _scrollIntoView: function( item ) { - var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; - if ( this._hasScroll() ) { - borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0; - paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0; - offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; - scroll = this.activeMenu.scrollTop(); - elementHeight = this.activeMenu.height(); - itemHeight = item.outerHeight(); - - if ( offset < 0 ) { - this.activeMenu.scrollTop( scroll + offset ); - } else if ( offset + itemHeight > elementHeight ) { - this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); - } - } - }, - - blur: function( event, fromFocus ) { - if ( !fromFocus ) { - clearTimeout( this.timer ); - } - - if ( !this.active ) { - return; - } - - this._removeClass( this.active.children( ".ui-menu-item-wrapper" ), - null, "ui-state-active" ); - - this._trigger( "blur", event, { item: this.active } ); - this.active = null; - }, - - _startOpening: function( submenu ) { - clearTimeout( this.timer ); - - // Don't open if already open fixes a Firefox bug that caused a .5 pixel - // shift in the submenu position when mousing over the caret icon - if ( submenu.attr( "aria-hidden" ) !== "true" ) { - return; - } - - this.timer = this._delay( function() { - this._close(); - this._open( submenu ); - }, this.delay ); - }, - - _open: function( submenu ) { - var position = $.extend( { - of: this.active - }, this.options.position ); - - clearTimeout( this.timer ); - this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) - .hide() - .attr( "aria-hidden", "true" ); - - submenu - .show() - .removeAttr( "aria-hidden" ) - .attr( "aria-expanded", "true" ) - .position( position ); - }, - - collapseAll: function( event, all ) { - clearTimeout( this.timer ); - this.timer = this._delay( function() { - - // If we were passed an event, look for the submenu that contains the event - var currentMenu = all ? this.element : - $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); - - // If we found no valid submenu ancestor, use the main menu to close all - // sub menus anyway - if ( !currentMenu.length ) { - currentMenu = this.element; - } - - this._close( currentMenu ); - - this.blur( event ); - - // Work around active item staying active after menu is blurred - this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); - - this.activeMenu = currentMenu; - }, this.delay ); - }, - - // With no arguments, closes the currently active menu - if nothing is active - // it closes all menus. If passed an argument, it will search for menus BELOW - _close: function( startMenu ) { - if ( !startMenu ) { - startMenu = this.active ? this.active.parent() : this.element; - } - - startMenu.find( ".ui-menu" ) - .hide() - .attr( "aria-hidden", "true" ) - .attr( "aria-expanded", "false" ); - }, - - _closeOnDocumentClick: function( event ) { - return !$( event.target ).closest( ".ui-menu" ).length; - }, - - _isDivider: function( item ) { - - // Match hyphen, em dash, en dash - return !/[^\-\u2014\u2013\s]/.test( item.text() ); - }, - - collapse: function( event ) { - var newItem = this.active && - this.active.parent().closest( ".ui-menu-item", this.element ); - if ( newItem && newItem.length ) { - this._close(); - this.focus( event, newItem ); - } - }, - - expand: function( event ) { - var newItem = this.active && - this.active - .children( ".ui-menu " ) - .find( this.options.items ) - .first(); - - if ( newItem && newItem.length ) { - this._open( newItem.parent() ); - - // Delay so Firefox will not hide activedescendant change in expanding submenu from AT - this._delay( function() { - this.focus( event, newItem ); - } ); - } - }, - - next: function( event ) { - this._move( "next", "first", event ); - }, - - previous: function( event ) { - this._move( "prev", "last", event ); - }, - - isFirstItem: function() { - return this.active && !this.active.prevAll( ".ui-menu-item" ).length; - }, - - isLastItem: function() { - return this.active && !this.active.nextAll( ".ui-menu-item" ).length; - }, - - _move: function( direction, filter, event ) { - var next; - if ( this.active ) { - if ( direction === "first" || direction === "last" ) { - next = this.active - [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) - .eq( -1 ); - } else { - next = this.active - [ direction + "All" ]( ".ui-menu-item" ) - .eq( 0 ); - } - } - if ( !next || !next.length || !this.active ) { - next = this.activeMenu.find( this.options.items )[ filter ](); - } - - this.focus( event, next ); - }, - - nextPage: function( event ) { - var item, base, height; - - if ( !this.active ) { - this.next( event ); - return; - } - if ( this.isLastItem() ) { - return; - } - if ( this._hasScroll() ) { - base = this.active.offset().top; - height = this.element.height(); - this.active.nextAll( ".ui-menu-item" ).each( function() { - item = $( this ); - return item.offset().top - base - height < 0; - } ); - - this.focus( event, item ); - } else { - this.focus( event, this.activeMenu.find( this.options.items ) - [ !this.active ? "first" : "last" ]() ); - } - }, - - previousPage: function( event ) { - var item, base, height; - if ( !this.active ) { - this.next( event ); - return; - } - if ( this.isFirstItem() ) { - return; - } - if ( this._hasScroll() ) { - base = this.active.offset().top; - height = this.element.height(); - this.active.prevAll( ".ui-menu-item" ).each( function() { - item = $( this ); - return item.offset().top - base + height > 0; - } ); - - this.focus( event, item ); - } else { - this.focus( event, this.activeMenu.find( this.options.items ).first() ); - } - }, - - _hasScroll: function() { - return this.element.outerHeight() < this.element.prop( "scrollHeight" ); - }, - - select: function( event ) { - - // TODO: It should never be possible to not have an active item at this - // point, but the tests don't trigger mouseenter before click. - this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); - var ui = { item: this.active }; - if ( !this.active.has( ".ui-menu" ).length ) { - this.collapseAll( event, true ); - } - this._trigger( "select", event, ui ); - }, - - _filterMenuItems: function( character ) { - var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), - regex = new RegExp( "^" + escapedCharacter, "i" ); - - return this.activeMenu - .find( this.options.items ) - - // Only match on items, not dividers or other content (#10571) - .filter( ".ui-menu-item" ) - .filter( function() { - return regex.test( - $.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) ); - } ); - } -} ); - - -/*! - * jQuery UI Autocomplete 1.12.1 + var widgetsMenu = $.widget( "ui.menu", { + version: "1.13.0", + defaultElement: "<ul>", + delay: 300, + options: { + icons: { + submenu: "ui-icon-caret-1-e" + }, + items: "> *", + menus: "ul", + position: { + my: "left top", + at: "right top" + }, + role: "menu", + + // Callbacks + blur: null, + focus: null, + select: null + }, + + _create: function() { + this.activeMenu = this.element; + + // Flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; + this.lastMousePosition = { x: null, y: null }; + this.element + .uniqueId() + .attr( { + role: this.options.role, + tabIndex: 0 + } ); + + this._addClass( "ui-menu", "ui-widget ui-widget-content" ); + this._on( { + + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item": function( event ) { + event.preventDefault(); + + this._activateItem( event ); + }, + "click .ui-menu-item": function( event ) { + var target = $( event.target ); + var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.select( event ); + + // Only set the mouseHandled flag if the event will bubble, see #9469. + if ( !event.isPropagationStopped() ) { + this.mouseHandled = true; + } + + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) && + active.closest( ".ui-menu" ).length ) { + + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } + } + }, + "mouseenter .ui-menu-item": "_activateItem", + "mousemove .ui-menu-item": "_activateItem", + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this._menuItems().first(); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } + }, + blur: function( event ) { + this._delay( function() { + var notContained = !$.contains( + this.element[ 0 ], + $.ui.safeActiveElement( this.document[ 0 ] ) + ); + if ( notContained ) { + this.collapseAll( event ); + } + } ); + }, + keydown: "_keydown" + } ); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( this._closeOnDocumentClick( event ) ) { + this.collapseAll( event, true ); + } + + // Reset the mouseHandled flag + this.mouseHandled = false; + } + } ); + }, + + _activateItem: function( event ) { + + // Ignore mouse events while typeahead is active, see #10458. + // Prevents focusing the wrong item when typeahead causes a scroll while the mouse + // is over an item in the menu + if ( this.previousFilter ) { + return; + } + + // If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356) + if ( event.clientX === this.lastMousePosition.x && + event.clientY === this.lastMousePosition.y ) { + return; + } + + this.lastMousePosition = { + x: event.clientX, + y: event.clientY + }; + + var actualTarget = $( event.target ).closest( ".ui-menu-item" ), + target = $( event.currentTarget ); + + // Ignore bubbled events on parent items, see #11641 + if ( actualTarget[ 0 ] !== target[ 0 ] ) { + return; + } + + // If the item is already active, there's nothing to do + if ( target.is( ".ui-state-active" ) ) { + return; + } + + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + this._removeClass( target.siblings().children( ".ui-state-active" ), + null, "ui-state-active" ); + this.focus( event, target ); + }, + + _destroy: function() { + var items = this.element.find( ".ui-menu-item" ) + .removeAttr( "role aria-disabled" ), + submenus = items.children( ".ui-menu-item-wrapper" ) + .removeUniqueId() + .removeAttr( "tabIndex role aria-haspopup" ); + + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " + + "tabIndex" ) + .removeUniqueId() + .show(); + + submenus.children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-caret" ) ) { + elem.remove(); + } + } ); + }, + + _keydown: function( event ) { + var match, prev, character, skip, + preventDefault = true; + + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + skip = false; + + // Support number pad values + character = event.keyCode >= 96 && event.keyCode <= 105 ? + ( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode ); + + clearTimeout( this.filterTimer ); + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + + match = this._filterMenuItems( character ); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + match = this._filterMenuItems( character ); + } + + if ( match.length ) { + this.focus( event, match ); + this.previousFilter = character; + this.filterTimer = this._delay( function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } + + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.children( "[aria-haspopup='true']" ).length ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + + refresh: function() { + var menus, items, newSubmenus, newItems, newWrappers, + that = this, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length ); + + // Initialize nested menus + newSubmenus = submenus.filter( ":not(.ui-menu)" ) + .hide() + .attr( { + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" + } ) + .each( function() { + var menu = $( this ), + item = menu.prev(), + submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true ); + + that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon ); + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCaret ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + } ); + + this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" ); + + menus = submenus.add( this.element ); + items = menus.find( this.options.items ); + + // Initialize menu-items containing spaces and/or dashes only as dividers + items.not( ".ui-menu-item" ).each( function() { + var item = $( this ); + if ( that._isDivider( item ) ) { + that._addClass( item, "ui-menu-divider", "ui-widget-content" ); + } + } ); + + // Don't refresh list items that are already adapted + newItems = items.not( ".ui-menu-item, .ui-menu-divider" ); + newWrappers = newItems.children() + .not( ".ui-menu" ) + .uniqueId() + .attr( { + tabIndex: -1, + role: this._itemRole() + } ); + this._addClass( newItems, "ui-menu-item" ) + ._addClass( newWrappers, "ui-menu-item-wrapper" ); + + // Add aria-disabled attribute to any disabled menu item + items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } + }, + + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + var icons = this.element.find( ".ui-menu-icon" ); + this._removeClass( icons, null, this.options.icons.submenu ) + ._addClass( icons, null, value.submenu ); + } + this._super( key, value ); + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this.element.attr( "aria-disabled", String( value ) ); + this._toggleClass( null, "ui-state-disabled", !!value ); + }, + + focus: function( event, item ) { + var nested, focused, activeParent; + this.blur( event, event && event.type === "focus" ); + + this._scrollIntoView( item ); + + this.active = item.first(); + + focused = this.active.children( ".ui-menu-item-wrapper" ); + this._addClass( focused, null, "ui-state-active" ); + + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } + + // Highlight active parent menu item, if any + activeParent = this.active + .parent() + .closest( ".ui-menu-item" ) + .children( ".ui-menu-item-wrapper" ); + this._addClass( activeParent, null, "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay( function() { + this._close(); + }, this.delay ); + } + + nested = item.children( ".ui-menu" ); + if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { + this._startOpening( nested ); + } + this.activeMenu = item.parent(); + + this._trigger( "focus", event, { item: item } ); + }, + + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.outerHeight(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } + + if ( !this.active ) { + return; + } + + this._removeClass( this.active.children( ".ui-menu-item-wrapper" ), + null, "ui-state-active" ); + + this._trigger( "blur", event, { item: this.active } ); + this.active = null; + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the caret icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { + return; + } + + this.timer = this._delay( function() { + this._close(); + this._open( submenu ); + }, this.delay ); + }, + + _open: function( submenu ) { + var position = $.extend( { + of: this.active + }, this.options.position ); + + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); + }, + + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay( function() { + + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // If we found no valid submenu ancestor, use the main menu to close all + // sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); + + this.blur( event ); + + // Work around active item staying active after menu is blurred + this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); + + this.activeMenu = currentMenu; + }, all ? 0 : this.delay ); + }, + + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu.find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ); + }, + + _closeOnDocumentClick: function( event ) { + return !$( event.target ).closest( ".ui-menu" ).length; + }, + + _isDivider: function( item ) { + + // Match hyphen, em dash, en dash + return !/[^\-\u2014\u2013\s]/.test( item.text() ); + }, + + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); + } + }, + + expand: function( event ) { + var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first(); + + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); + + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay( function() { + this.focus( event, newItem ); + } ); + } + }, + + next: function( event ) { + this._move( "next", "first", event ); + }, + + previous: function( event ) { + this._move( "prev", "last", event ); + }, + + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; + }, + + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; + }, + + _menuItems: function( menu ) { + return ( menu || this.element ) + .find( this.options.items ) + .filter( ".ui-menu-item" ); + }, + + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .last(); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .first(); + } + } + if ( !next || !next.length || !this.active ) { + next = this._menuItems( this.activeMenu )[ filter ](); + } + + this.focus( event, next ); + }, + + nextPage: function( event ) { + var item, base, height; + + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.innerHeight(); + + // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back. + if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) { + height += this.element[ 0 ].offsetHeight - this.element.outerHeight(); + } + + this.active.nextAll( ".ui-menu-item" ).each( function() { + item = $( this ); + return item.offset().top - base - height < 0; + } ); + + this.focus( event, item ); + } else { + this.focus( event, this._menuItems( this.activeMenu ) + [ !this.active ? "first" : "last" ]() ); + } + }, + + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.innerHeight(); + + // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back. + if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) { + height += this.element[ 0 ].offsetHeight - this.element.outerHeight(); + } + + this.active.prevAll( ".ui-menu-item" ).each( function() { + item = $( this ); + return item.offset().top - base + height > 0; + } ); + + this.focus( event, item ); + } else { + this.focus( event, this._menuItems( this.activeMenu ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); + } + this._trigger( "select", event, ui ); + }, + + _filterMenuItems: function( character ) { + var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), + regex = new RegExp( "^" + escapedCharacter, "i" ); + + return this.activeMenu + .find( this.options.items ) + + // Only match on items, not dividers or other content (#10571) + .filter( ".ui-menu-item" ) + .filter( function() { + return regex.test( + String.prototype.trim.call( + $( this ).children( ".ui-menu-item-wrapper" ).text() ) ); + } ); + } + } ); + + + /*! + * jQuery UI Autocomplete 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -8007,653 +8143,640 @@ var widgetsMenu = $.widget( "ui.menu", { //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.autocomplete", { - version: "1.12.1", - defaultElement: "<input>", - options: { - appendTo: null, - autoFocus: false, - delay: 300, - minLength: 1, - position: { - my: "left top", - at: "left bottom", - collision: "none" - }, - source: null, - - // Callbacks - change: null, - close: null, - focus: null, - open: null, - response: null, - search: null, - select: null - }, - - requestIndex: 0, - pending: 0, - - _create: function() { - - // Some browsers only repeat keydown events, not keypress events, - // so we use the suppressKeyPress flag to determine if we've already - // handled the keydown event. #7269 - // Unfortunately the code for & in keypress is the same as the up arrow, - // so we use the suppressKeyPressRepeat flag to avoid handling keypress - // events when we know the keydown event was used to modify the - // search term. #7799 - var suppressKeyPress, suppressKeyPressRepeat, suppressInput, - nodeName = this.element[ 0 ].nodeName.toLowerCase(), - isTextarea = nodeName === "textarea", - isInput = nodeName === "input"; - - // Textareas are always multi-line - // Inputs are always single-line, even if inside a contentEditable element - // IE also treats inputs as contentEditable - // All other element types are determined by whether or not they're contentEditable - this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element ); - - this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; - this.isNewMenu = true; - - this._addClass( "ui-autocomplete-input" ); - this.element.attr( "autocomplete", "off" ); - - this._on( this.element, { - keydown: function( event ) { - if ( this.element.prop( "readOnly" ) ) { - suppressKeyPress = true; - suppressInput = true; - suppressKeyPressRepeat = true; - return; - } - - suppressKeyPress = false; - suppressInput = false; - suppressKeyPressRepeat = false; - var keyCode = $.ui.keyCode; - switch ( event.keyCode ) { - case keyCode.PAGE_UP: - suppressKeyPress = true; - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - suppressKeyPress = true; - this._move( "nextPage", event ); - break; - case keyCode.UP: - suppressKeyPress = true; - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - suppressKeyPress = true; - this._keyEvent( "next", event ); - break; - case keyCode.ENTER: - - // when menu is open and has focus - if ( this.menu.active ) { - - // #6055 - Opera still allows the keypress to occur - // which causes forms to submit - suppressKeyPress = true; - event.preventDefault(); - this.menu.select( event ); - } - break; - case keyCode.TAB: - if ( this.menu.active ) { - this.menu.select( event ); - } - break; - case keyCode.ESCAPE: - if ( this.menu.element.is( ":visible" ) ) { - if ( !this.isMultiLine ) { - this._value( this.term ); - } - this.close( event ); - - // Different browsers have different default behavior for escape - // Single press can mean undo or clear - // Double press in IE means clear the whole form - event.preventDefault(); - } - break; - default: - suppressKeyPressRepeat = true; - - // search timeout should be triggered before the input value is changed - this._searchTimeout( event ); - break; - } - }, - keypress: function( event ) { - if ( suppressKeyPress ) { - suppressKeyPress = false; - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - event.preventDefault(); - } - return; - } - if ( suppressKeyPressRepeat ) { - return; - } - - // Replicate some key handlers to allow them to repeat in Firefox and Opera - var keyCode = $.ui.keyCode; - switch ( event.keyCode ) { - case keyCode.PAGE_UP: - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - this._move( "nextPage", event ); - break; - case keyCode.UP: - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - this._keyEvent( "next", event ); - break; - } - }, - input: function( event ) { - if ( suppressInput ) { - suppressInput = false; - event.preventDefault(); - return; - } - this._searchTimeout( event ); - }, - focus: function() { - this.selectedItem = null; - this.previous = this._value(); - }, - blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; - } - - clearTimeout( this.searching ); - this.close( event ); - this._change( event ); - } - } ); - - this._initSource(); - this.menu = $( "<ul>" ) - .appendTo( this._appendTo() ) - .menu( { - - // disable ARIA support, the live region takes care of that - role: null - } ) - .hide() - .menu( "instance" ); - - this._addClass( this.menu.element, "ui-autocomplete", "ui-front" ); - this._on( this.menu.element, { - mousedown: function( event ) { - - // prevent moving focus out of the text field - event.preventDefault(); - - // IE doesn't prevent moving focus even with event.preventDefault() - // so we set a flag to know when we should ignore the blur event - this.cancelBlur = true; - this._delay( function() { - delete this.cancelBlur; - - // Support: IE 8 only - // Right clicking a menu item or selecting text from the menu items will - // result in focus moving out of the input. However, we've already received - // and ignored the blur event because of the cancelBlur flag set above. So - // we restore focus to ensure that the menu closes properly based on the user's - // next actions. - if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { - this.element.trigger( "focus" ); - } - } ); - }, - menufocus: function( event, ui ) { - var label, item; - - // support: Firefox - // Prevent accidental activation of menu items in Firefox (#7024 #9118) - if ( this.isNewMenu ) { - this.isNewMenu = false; - if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { - this.menu.blur(); - - this.document.one( "mousemove", function() { - $( event.target ).trigger( event.originalEvent ); - } ); - - return; - } - } - - item = ui.item.data( "ui-autocomplete-item" ); - if ( false !== this._trigger( "focus", event, { item: item } ) ) { - - // use value to match what will end up in the input, if it was a key event - if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { - this._value( item.value ); - } - } - - // Announce the value in the liveRegion - label = ui.item.attr( "aria-label" ) || item.value; - if ( label && $.trim( label ).length ) { - this.liveRegion.children().hide(); - $( "<div>" ).text( label ).appendTo( this.liveRegion ); - } - }, - menuselect: function( event, ui ) { - var item = ui.item.data( "ui-autocomplete-item" ), - previous = this.previous; - - // Only trigger when focus was lost (click on menu) - if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { - this.element.trigger( "focus" ); - this.previous = previous; - - // #6109 - IE triggers two focus events and the second - // is asynchronous, so we need to reset the previous - // term synchronously and asynchronously :-( - this._delay( function() { - this.previous = previous; - this.selectedItem = item; - } ); - } - - if ( false !== this._trigger( "select", event, { item: item } ) ) { - this._value( item.value ); - } - - // reset the term after the select event - // this allows custom select handling to work properly - this.term = this._value(); - - this.close( event ); - this.selectedItem = item; - } - } ); - - this.liveRegion = $( "<div>", { - role: "status", - "aria-live": "assertive", - "aria-relevant": "additions" - } ) - .appendTo( this.document[ 0 ].body ); - - this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); - - // Turning off autocomplete prevents the browser from remembering the - // value when navigating through history, so we re-enable autocomplete - // if the page is unloaded before the widget is destroyed. #7790 - this._on( this.window, { - beforeunload: function() { - this.element.removeAttr( "autocomplete" ); - } - } ); - }, - - _destroy: function() { - clearTimeout( this.searching ); - this.element.removeAttr( "autocomplete" ); - this.menu.element.remove(); - this.liveRegion.remove(); - }, - - _setOption: function( key, value ) { - this._super( key, value ); - if ( key === "source" ) { - this._initSource(); - } - if ( key === "appendTo" ) { - this.menu.element.appendTo( this._appendTo() ); - } - if ( key === "disabled" && value && this.xhr ) { - this.xhr.abort(); - } - }, - - _isEventTargetInWidget: function( event ) { - var menuElement = this.menu.element[ 0 ]; - - return event.target === this.element[ 0 ] || - event.target === menuElement || - $.contains( menuElement, event.target ); - }, - - _closeOnClickOutside: function( event ) { - if ( !this._isEventTargetInWidget( event ) ) { - this.close(); - } - }, - - _appendTo: function() { - var element = this.options.appendTo; - - if ( element ) { - element = element.jquery || element.nodeType ? - $( element ) : - this.document.find( element ).eq( 0 ); - } - - if ( !element || !element[ 0 ] ) { - element = this.element.closest( ".ui-front, dialog" ); - } - - if ( !element.length ) { - element = this.document[ 0 ].body; - } - - return element; - }, - - _initSource: function() { - var array, url, - that = this; - if ( $.isArray( this.options.source ) ) { - array = this.options.source; - this.source = function( request, response ) { - response( $.ui.autocomplete.filter( array, request.term ) ); - }; - } else if ( typeof this.options.source === "string" ) { - url = this.options.source; - this.source = function( request, response ) { - if ( that.xhr ) { - that.xhr.abort(); - } - that.xhr = $.ajax( { - url: url, - data: request, - dataType: "json", - success: function( data ) { - response( data ); - }, - error: function() { - response( [] ); - } - } ); - }; - } else { - this.source = this.options.source; - } - }, - - _searchTimeout: function( event ) { - clearTimeout( this.searching ); - this.searching = this._delay( function() { - - // Search if the value has changed, or if the user retypes the same value (see #7434) - var equalValues = this.term === this._value(), - menuVisible = this.menu.element.is( ":visible" ), - modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; - - if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { - this.selectedItem = null; - this.search( null, event ); - } - }, this.options.delay ); - }, - - search: function( value, event ) { - value = value != null ? value : this._value(); - - // Always save the actual value, not the one passed as an argument - this.term = this._value(); - - if ( value.length < this.options.minLength ) { - return this.close( event ); - } - - if ( this._trigger( "search", event ) === false ) { - return; - } - - return this._search( value ); - }, - - _search: function( value ) { - this.pending++; - this._addClass( "ui-autocomplete-loading" ); - this.cancelSearch = false; - - this.source( { term: value }, this._response() ); - }, - - _response: function() { - var index = ++this.requestIndex; - - return $.proxy( function( content ) { - if ( index === this.requestIndex ) { - this.__response( content ); - } - - this.pending--; - if ( !this.pending ) { - this._removeClass( "ui-autocomplete-loading" ); - } - }, this ); - }, - - __response: function( content ) { - if ( content ) { - content = this._normalize( content ); - } - this._trigger( "response", null, { content: content } ); - if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { - this._suggest( content ); - this._trigger( "open" ); - } else { - - // use ._close() instead of .close() so we don't cancel future searches - this._close(); - } - }, - - close: function( event ) { - this.cancelSearch = true; - this._close( event ); - }, - - _close: function( event ) { - - // Remove the handler that closes the menu on outside clicks - this._off( this.document, "mousedown" ); - - if ( this.menu.element.is( ":visible" ) ) { - this.menu.element.hide(); - this.menu.blur(); - this.isNewMenu = true; - this._trigger( "close", event ); - } - }, - - _change: function( event ) { - if ( this.previous !== this._value() ) { - this._trigger( "change", event, { item: this.selectedItem } ); - } - }, - - _normalize: function( items ) { - - // assume all items have the right format when the first item is complete - if ( items.length && items[ 0 ].label && items[ 0 ].value ) { - return items; - } - return $.map( items, function( item ) { - if ( typeof item === "string" ) { - return { - label: item, - value: item - }; - } - return $.extend( {}, item, { - label: item.label || item.value, - value: item.value || item.label - } ); - } ); - }, - - _suggest: function( items ) { - var ul = this.menu.element.empty(); - this._renderMenu( ul, items ); - this.isNewMenu = true; - this.menu.refresh(); - - // Size and position menu - ul.show(); - this._resizeMenu(); - ul.position( $.extend( { - of: this.element - }, this.options.position ) ); - - if ( this.options.autoFocus ) { - this.menu.next(); - } - - // Listen for interactions outside of the widget (#6642) - this._on( this.document, { - mousedown: "_closeOnClickOutside" - } ); - }, - - _resizeMenu: function() { - var ul = this.menu.element; - ul.outerWidth( Math.max( - - // Firefox wraps long text (possibly a rounding bug) - // so we add 1px to avoid the wrapping (#7513) - ul.width( "" ).outerWidth() + 1, - this.element.outerWidth() - ) ); - }, - - _renderMenu: function( ul, items ) { - var that = this; - $.each( items, function( index, item ) { - that._renderItemData( ul, item ); - } ); - }, - - _renderItemData: function( ul, item ) { - return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); - }, - - _renderItem: function( ul, item ) { - return $( "<li>" ) - .append( $( "<div>" ).text( item.label ) ) - .appendTo( ul ); - }, - - _move: function( direction, event ) { - if ( !this.menu.element.is( ":visible" ) ) { - this.search( null, event ); - return; - } - if ( this.menu.isFirstItem() && /^previous/.test( direction ) || - this.menu.isLastItem() && /^next/.test( direction ) ) { - - if ( !this.isMultiLine ) { - this._value( this.term ); - } - - this.menu.blur(); - return; - } - this.menu[ direction ]( event ); - }, - - widget: function() { - return this.menu.element; - }, - - _value: function() { - return this.valueMethod.apply( this.element, arguments ); - }, - - _keyEvent: function( keyEvent, event ) { - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - this._move( keyEvent, event ); - - // Prevents moving cursor to beginning/end of the text field in some browsers - event.preventDefault(); - } - }, - - // Support: Chrome <=50 - // We should be able to just use this.element.prop( "isContentEditable" ) - // but hidden elements always report false in Chrome. - // https://code.google.com/p/chromium/issues/detail?id=313082 - _isContentEditable: function( element ) { - if ( !element.length ) { - return false; - } - - var editable = element.prop( "contentEditable" ); - - if ( editable === "inherit" ) { - return this._isContentEditable( element.parent() ); - } - - return editable === "true"; - } -} ); - -$.extend( $.ui.autocomplete, { - escapeRegex: function( value ) { - return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); - }, - filter: function( array, term ) { - var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); - return $.grep( array, function( value ) { - return matcher.test( value.label || value.value || value ); - } ); - } -} ); + $.widget( "ui.autocomplete", { + version: "1.13.0", + defaultElement: "<input>", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // Callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + requestIndex: 0, + pending: 0, + + _create: function() { + + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[ 0 ].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + // Textareas are always multi-line + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + // All other element types are determined by whether or not they're contentEditable + this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this._addClass( "ui-autocomplete-input" ); + this.element.attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + + // when menu is open and has focus + if ( this.menu.active ) { + + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + if ( !this.isMultiLine ) { + this._value( this.term ); + } + this.close( event ); + + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + event.preventDefault(); + } + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // Replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + } ); + + this._initSource(); + this.menu = $( "<ul>" ) + .appendTo( this._appendTo() ) + .menu( { + + // disable ARIA support, the live region takes care of that + role: null + } ) + .hide() + + // Support: IE 11 only, Edge <= 14 + // For other browsers, we preventDefault() on the mousedown event + // to keep the dropdown from taking focus from the input. This doesn't + // work for IE/Edge, causing problems with selection and scrolling (#9638) + // Happily, IE and Edge support an "unselectable" attribute that + // prevents an element from receiving focus, exactly what we want here. + .attr( { + "unselectable": "on" + } ) + .menu( "instance" ); + + this._addClass( this.menu.element, "ui-autocomplete", "ui-front" ); + this._on( this.menu.element, { + mousedown: function( event ) { + + // Prevent moving focus out of the text field + event.preventDefault(); + }, + menufocus: function( event, ui ) { + var label, item; + + // support: Firefox + // Prevent accidental activation of menu items in Firefox (#7024 #9118) + if ( this.isNewMenu ) { + this.isNewMenu = false; + if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { + this.menu.blur(); + + this.document.one( "mousemove", function() { + $( event.target ).trigger( event.originalEvent ); + } ); + + return; + } + } + + item = ui.item.data( "ui-autocomplete-item" ); + if ( false !== this._trigger( "focus", event, { item: item } ) ) { + + // use value to match what will end up in the input, if it was a key event + if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { + this._value( item.value ); + } + } + + // Announce the value in the liveRegion + label = ui.item.attr( "aria-label" ) || item.value; + if ( label && String.prototype.trim.call( label ).length ) { + this.liveRegion.children().hide(); + $( "<div>" ).text( label ).appendTo( this.liveRegion ); + } + }, + menuselect: function( event, ui ) { + var item = ui.item.data( "ui-autocomplete-item" ), + previous = this.previous; + + // Only trigger when focus was lost (click on menu) + if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { + this.element.trigger( "focus" ); + this.previous = previous; + + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + this._delay( function() { + this.previous = previous; + this.selectedItem = item; + } ); + } + + if ( false !== this._trigger( "select", event, { item: item } ) ) { + this._value( item.value ); + } + + // reset the term after the select event + // this allows custom select handling to work properly + this.term = this._value(); + + this.close( event ); + this.selectedItem = item; + } + } ); + + this.liveRegion = $( "<div>", { + role: "status", + "aria-live": "assertive", + "aria-relevant": "additions" + } ) + .appendTo( this.document[ 0 ].body ); + + this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); + + // Turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + } ); + }, + + _destroy: function() { + clearTimeout( this.searching ); + this.element.removeAttr( "autocomplete" ); + this.menu.element.remove(); + this.liveRegion.remove(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( this._appendTo() ); + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _isEventTargetInWidget: function( event ) { + var menuElement = this.menu.element[ 0 ]; + + return event.target === this.element[ 0 ] || + event.target === menuElement || + $.contains( menuElement, event.target ); + }, + + _closeOnClickOutside: function( event ) { + if ( !this._isEventTargetInWidget( event ) ) { + this.close(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element || !element[ 0 ] ) { + element = this.element.closest( ".ui-front, dialog" ); + } + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _initSource: function() { + var array, url, + that = this; + if ( Array.isArray( this.options.source ) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter( array, request.term ) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( that.xhr ) { + that.xhr.abort(); + } + that.xhr = $.ajax( { + url: url, + data: request, + dataType: "json", + success: function( data ) { + response( data ); + }, + error: function() { + response( [] ); + } + } ); + }; + } else { + this.source = this.options.source; + } + }, + + _searchTimeout: function( event ) { + clearTimeout( this.searching ); + this.searching = this._delay( function() { + + // Search if the value has changed, or if the user retypes the same value (see #7434) + var equalValues = this.term === this._value(), + menuVisible = this.menu.element.is( ":visible" ), + modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + + if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { + this.selectedItem = null; + this.search( null, event ); + } + }, this.options.delay ); + }, + + search: function( value, event ) { + value = value != null ? value : this._value(); + + // Always save the actual value, not the one passed as an argument + this.term = this._value(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this._addClass( "ui-autocomplete-loading" ); + this.cancelSearch = false; + + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var index = ++this.requestIndex; + + return function( content ) { + if ( index === this.requestIndex ) { + this.__response( content ); + } + + this.pending--; + if ( !this.pending ) { + this._removeClass( "ui-autocomplete-loading" ); + } + }.bind( this ); + }, + + __response: function( content ) { + if ( content ) { + content = this._normalize( content ); + } + this._trigger( "response", null, { content: content } ); + if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { + this._suggest( content ); + this._trigger( "open" ); + } else { + + // use ._close() instead of .close() so we don't cancel future searches + this._close(); + } + }, + + close: function( event ) { + this.cancelSearch = true; + this._close( event ); + }, + + _close: function( event ) { + + // Remove the handler that closes the menu on outside clicks + this._off( this.document, "mousedown" ); + + if ( this.menu.element.is( ":visible" ) ) { + this.menu.element.hide(); + this.menu.blur(); + this.isNewMenu = true; + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this._value() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + + // assume all items have the right format when the first item is complete + if ( items.length && items[ 0 ].label && items[ 0 ].value ) { + return items; + } + return $.map( items, function( item ) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend( {}, item, { + label: item.label || item.value, + value: item.value || item.label + } ); + } ); + }, + + _suggest: function( items ) { + var ul = this.menu.element.empty(); + this._renderMenu( ul, items ); + this.isNewMenu = true; + this.menu.refresh(); + + // Size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend( { + of: this.element + }, this.options.position ) ); + + if ( this.options.autoFocus ) { + this.menu.next(); + } + + // Listen for interactions outside of the widget (#6642) + this._on( this.document, { + mousedown: "_closeOnClickOutside" + } ); + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + } ); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + }, + + _renderItem: function( ul, item ) { + return $( "<li>" ) + .append( $( "<div>" ).text( item.label ) ) + .appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is( ":visible" ) ) { + this.search( null, event ); + return; + } + if ( this.menu.isFirstItem() && /^previous/.test( direction ) || + this.menu.isLastItem() && /^next/.test( direction ) ) { + + if ( !this.isMultiLine ) { + this._value( this.term ); + } + + this.menu.blur(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + }, + + _value: function() { + return this.valueMethod.apply( this.element, arguments ); + }, + + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // Prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } + }, + + // Support: Chrome <=50 + // We should be able to just use this.element.prop( "isContentEditable" ) + // but hidden elements always report false in Chrome. + // https://code.google.com/p/chromium/issues/detail?id=313082 + _isContentEditable: function( element ) { + if ( !element.length ) { + return false; + } + + var editable = element.prop( "contentEditable" ); + + if ( editable === "inherit" ) { + return this._isContentEditable( element.parent() ); + } + + return editable === "true"; + } + } ); + + $.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + }, + filter: function( array, term ) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); + return $.grep( array, function( value ) { + return matcher.test( value.label || value.value || value ); + } ); + } + } ); // Live region extension, adding a `messages` option // NOTE: This is an experimental API. We are still investigating // a full solution for string manipulation and internationalization. -$.widget( "ui.autocomplete", $.ui.autocomplete, { - options: { - messages: { - noResults: "No search results.", - results: function( amount ) { - return amount + ( amount > 1 ? " results are" : " result is" ) + - " available, use up and down arrow keys to navigate."; - } - } - }, - - __response: function( content ) { - var message; - this._superApply( arguments ); - if ( this.options.disabled || this.cancelSearch ) { - return; - } - if ( content && content.length ) { - message = this.options.messages.results( content.length ); - } else { - message = this.options.messages.noResults; - } - this.liveRegion.children().hide(); - $( "<div>" ).text( message ).appendTo( this.liveRegion ); - } -} ); - -var widgetsAutocomplete = $.ui.autocomplete; - - -/*! - * jQuery UI Controlgroup 1.12.1 + $.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } + } + }, + + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.children().hide(); + $( "<div>" ).text( message ).appendTo( this.liveRegion ); + } + } ); + + var widgetsAutocomplete = $.ui.autocomplete; + + + /*! + * jQuery UI Controlgroup 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -8671,274 +8794,274 @@ var widgetsAutocomplete = $.ui.autocomplete; //>>css.theme: ../../themes/base/theme.css -var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g; - -var widgetsControlgroup = $.widget( "ui.controlgroup", { - version: "1.12.1", - defaultElement: "<div>", - options: { - direction: "horizontal", - disabled: null, - onlyVisible: true, - items: { - "button": "input[type=button], input[type=submit], input[type=reset], button, a", - "controlgroupLabel": ".ui-controlgroup-label", - "checkboxradio": "input[type='checkbox'], input[type='radio']", - "selectmenu": "select", - "spinner": ".ui-spinner-input" - } - }, - - _create: function() { - this._enhance(); - }, - - // To support the enhanced option in jQuery Mobile, we isolate DOM manipulation - _enhance: function() { - this.element.attr( "role", "toolbar" ); - this.refresh(); - }, - - _destroy: function() { - this._callChildMethod( "destroy" ); - this.childWidgets.removeData( "ui-controlgroup-data" ); - this.element.removeAttr( "role" ); - if ( this.options.items.controlgroupLabel ) { - this.element - .find( this.options.items.controlgroupLabel ) - .find( ".ui-controlgroup-label-contents" ) - .contents().unwrap(); - } - }, - - _initWidgets: function() { - var that = this, - childWidgets = []; - - // First we iterate over each of the items options - $.each( this.options.items, function( widget, selector ) { - var labels; - var options = {}; - - // Make sure the widget has a selector set - if ( !selector ) { - return; - } - - if ( widget === "controlgroupLabel" ) { - labels = that.element.find( selector ); - labels.each( function() { - var element = $( this ); - - if ( element.children( ".ui-controlgroup-label-contents" ).length ) { - return; - } - element.contents() - .wrapAll( "<span class='ui-controlgroup-label-contents'></span>" ); - } ); - that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" ); - childWidgets = childWidgets.concat( labels.get() ); - return; - } - - // Make sure the widget actually exists - if ( !$.fn[ widget ] ) { - return; - } - - // We assume everything is in the middle to start because we can't determine - // first / last elements until all enhancments are done. - if ( that[ "_" + widget + "Options" ] ) { - options = that[ "_" + widget + "Options" ]( "middle" ); - } else { - options = { classes: {} }; - } - - // Find instances of this widget inside controlgroup and init them - that.element - .find( selector ) - .each( function() { - var element = $( this ); - var instance = element[ widget ]( "instance" ); - - // We need to clone the default options for this type of widget to avoid - // polluting the variable options which has a wider scope than a single widget. - var instanceOptions = $.widget.extend( {}, options ); - - // If the button is the child of a spinner ignore it - // TODO: Find a more generic solution - if ( widget === "button" && element.parent( ".ui-spinner" ).length ) { - return; - } - - // Create the widget if it doesn't exist - if ( !instance ) { - instance = element[ widget ]()[ widget ]( "instance" ); - } - if ( instance ) { - instanceOptions.classes = - that._resolveClassesValues( instanceOptions.classes, instance ); - } - element[ widget ]( instanceOptions ); - - // Store an instance of the controlgroup to be able to reference - // from the outermost element for changing options and refresh - var widgetElement = element[ widget ]( "widget" ); - $.data( widgetElement[ 0 ], "ui-controlgroup-data", - instance ? instance : element[ widget ]( "instance" ) ); - - childWidgets.push( widgetElement[ 0 ] ); - } ); - } ); - - this.childWidgets = $( $.unique( childWidgets ) ); - this._addClass( this.childWidgets, "ui-controlgroup-item" ); - }, - - _callChildMethod: function( method ) { - this.childWidgets.each( function() { - var element = $( this ), - data = element.data( "ui-controlgroup-data" ); - if ( data && data[ method ] ) { - data[ method ](); - } - } ); - }, - - _updateCornerClass: function( element, position ) { - var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all"; - var add = this._buildSimpleOptions( position, "label" ).classes.label; - - this._removeClass( element, null, remove ); - this._addClass( element, null, add ); - }, - - _buildSimpleOptions: function( position, key ) { - var direction = this.options.direction === "vertical"; - var result = { - classes: {} - }; - result.classes[ key ] = { - "middle": "", - "first": "ui-corner-" + ( direction ? "top" : "left" ), - "last": "ui-corner-" + ( direction ? "bottom" : "right" ), - "only": "ui-corner-all" - }[ position ]; - - return result; - }, - - _spinnerOptions: function( position ) { - var options = this._buildSimpleOptions( position, "ui-spinner" ); - - options.classes[ "ui-spinner-up" ] = ""; - options.classes[ "ui-spinner-down" ] = ""; - - return options; - }, - - _buttonOptions: function( position ) { - return this._buildSimpleOptions( position, "ui-button" ); - }, - - _checkboxradioOptions: function( position ) { - return this._buildSimpleOptions( position, "ui-checkboxradio-label" ); - }, - - _selectmenuOptions: function( position ) { - var direction = this.options.direction === "vertical"; - return { - width: direction ? "auto" : false, - classes: { - middle: { - "ui-selectmenu-button-open": "", - "ui-selectmenu-button-closed": "" - }, - first: { - "ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ), - "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" ) - }, - last: { - "ui-selectmenu-button-open": direction ? "" : "ui-corner-tr", - "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" ) - }, - only: { - "ui-selectmenu-button-open": "ui-corner-top", - "ui-selectmenu-button-closed": "ui-corner-all" - } - - }[ position ] - }; - }, - - _resolveClassesValues: function( classes, instance ) { - var result = {}; - $.each( classes, function( key ) { - var current = instance.options.classes[ key ] || ""; - current = $.trim( current.replace( controlgroupCornerRegex, "" ) ); - result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " ); - } ); - return result; - }, - - _setOption: function( key, value ) { - if ( key === "direction" ) { - this._removeClass( "ui-controlgroup-" + this.options.direction ); - } - - this._super( key, value ); - if ( key === "disabled" ) { - this._callChildMethod( value ? "disable" : "enable" ); - return; - } - - this.refresh(); - }, - - refresh: function() { - var children, - that = this; - - this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction ); - - if ( this.options.direction === "horizontal" ) { - this._addClass( null, "ui-helper-clearfix" ); - } - this._initWidgets(); - - children = this.childWidgets; - - // We filter here because we need to track all childWidgets not just the visible ones - if ( this.options.onlyVisible ) { - children = children.filter( ":visible" ); - } - - if ( children.length ) { - - // We do this last because we need to make sure all enhancment is done - // before determining first and last - $.each( [ "first", "last" ], function( index, value ) { - var instance = children[ value ]().data( "ui-controlgroup-data" ); - - if ( instance && that[ "_" + instance.widgetName + "Options" ] ) { - var options = that[ "_" + instance.widgetName + "Options" ]( - children.length === 1 ? "only" : value - ); - options.classes = that._resolveClassesValues( options.classes, instance ); - instance.element[ instance.widgetName ]( options ); - } else { - that._updateCornerClass( children[ value ](), value ); - } - } ); - - // Finally call the refresh method on each of the child widgets. - this._callChildMethod( "refresh" ); - } - } -} ); - -/*! - * jQuery UI Checkboxradio 1.12.1 + var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g; + + var widgetsControlgroup = $.widget( "ui.controlgroup", { + version: "1.13.0", + defaultElement: "<div>", + options: { + direction: "horizontal", + disabled: null, + onlyVisible: true, + items: { + "button": "input[type=button], input[type=submit], input[type=reset], button, a", + "controlgroupLabel": ".ui-controlgroup-label", + "checkboxradio": "input[type='checkbox'], input[type='radio']", + "selectmenu": "select", + "spinner": ".ui-spinner-input" + } + }, + + _create: function() { + this._enhance(); + }, + + // To support the enhanced option in jQuery Mobile, we isolate DOM manipulation + _enhance: function() { + this.element.attr( "role", "toolbar" ); + this.refresh(); + }, + + _destroy: function() { + this._callChildMethod( "destroy" ); + this.childWidgets.removeData( "ui-controlgroup-data" ); + this.element.removeAttr( "role" ); + if ( this.options.items.controlgroupLabel ) { + this.element + .find( this.options.items.controlgroupLabel ) + .find( ".ui-controlgroup-label-contents" ) + .contents().unwrap(); + } + }, + + _initWidgets: function() { + var that = this, + childWidgets = []; + + // First we iterate over each of the items options + $.each( this.options.items, function( widget, selector ) { + var labels; + var options = {}; + + // Make sure the widget has a selector set + if ( !selector ) { + return; + } + + if ( widget === "controlgroupLabel" ) { + labels = that.element.find( selector ); + labels.each( function() { + var element = $( this ); + + if ( element.children( ".ui-controlgroup-label-contents" ).length ) { + return; + } + element.contents() + .wrapAll( "<span class='ui-controlgroup-label-contents'></span>" ); + } ); + that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" ); + childWidgets = childWidgets.concat( labels.get() ); + return; + } + + // Make sure the widget actually exists + if ( !$.fn[ widget ] ) { + return; + } + + // We assume everything is in the middle to start because we can't determine + // first / last elements until all enhancments are done. + if ( that[ "_" + widget + "Options" ] ) { + options = that[ "_" + widget + "Options" ]( "middle" ); + } else { + options = { classes: {} }; + } + + // Find instances of this widget inside controlgroup and init them + that.element + .find( selector ) + .each( function() { + var element = $( this ); + var instance = element[ widget ]( "instance" ); + + // We need to clone the default options for this type of widget to avoid + // polluting the variable options which has a wider scope than a single widget. + var instanceOptions = $.widget.extend( {}, options ); + + // If the button is the child of a spinner ignore it + // TODO: Find a more generic solution + if ( widget === "button" && element.parent( ".ui-spinner" ).length ) { + return; + } + + // Create the widget if it doesn't exist + if ( !instance ) { + instance = element[ widget ]()[ widget ]( "instance" ); + } + if ( instance ) { + instanceOptions.classes = + that._resolveClassesValues( instanceOptions.classes, instance ); + } + element[ widget ]( instanceOptions ); + + // Store an instance of the controlgroup to be able to reference + // from the outermost element for changing options and refresh + var widgetElement = element[ widget ]( "widget" ); + $.data( widgetElement[ 0 ], "ui-controlgroup-data", + instance ? instance : element[ widget ]( "instance" ) ); + + childWidgets.push( widgetElement[ 0 ] ); + } ); + } ); + + this.childWidgets = $( $.uniqueSort( childWidgets ) ); + this._addClass( this.childWidgets, "ui-controlgroup-item" ); + }, + + _callChildMethod: function( method ) { + this.childWidgets.each( function() { + var element = $( this ), + data = element.data( "ui-controlgroup-data" ); + if ( data && data[ method ] ) { + data[ method ](); + } + } ); + }, + + _updateCornerClass: function( element, position ) { + var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all"; + var add = this._buildSimpleOptions( position, "label" ).classes.label; + + this._removeClass( element, null, remove ); + this._addClass( element, null, add ); + }, + + _buildSimpleOptions: function( position, key ) { + var direction = this.options.direction === "vertical"; + var result = { + classes: {} + }; + result.classes[ key ] = { + "middle": "", + "first": "ui-corner-" + ( direction ? "top" : "left" ), + "last": "ui-corner-" + ( direction ? "bottom" : "right" ), + "only": "ui-corner-all" + }[ position ]; + + return result; + }, + + _spinnerOptions: function( position ) { + var options = this._buildSimpleOptions( position, "ui-spinner" ); + + options.classes[ "ui-spinner-up" ] = ""; + options.classes[ "ui-spinner-down" ] = ""; + + return options; + }, + + _buttonOptions: function( position ) { + return this._buildSimpleOptions( position, "ui-button" ); + }, + + _checkboxradioOptions: function( position ) { + return this._buildSimpleOptions( position, "ui-checkboxradio-label" ); + }, + + _selectmenuOptions: function( position ) { + var direction = this.options.direction === "vertical"; + return { + width: direction ? "auto" : false, + classes: { + middle: { + "ui-selectmenu-button-open": "", + "ui-selectmenu-button-closed": "" + }, + first: { + "ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ), + "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" ) + }, + last: { + "ui-selectmenu-button-open": direction ? "" : "ui-corner-tr", + "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" ) + }, + only: { + "ui-selectmenu-button-open": "ui-corner-top", + "ui-selectmenu-button-closed": "ui-corner-all" + } + + }[ position ] + }; + }, + + _resolveClassesValues: function( classes, instance ) { + var result = {}; + $.each( classes, function( key ) { + var current = instance.options.classes[ key ] || ""; + current = String.prototype.trim.call( current.replace( controlgroupCornerRegex, "" ) ); + result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " ); + } ); + return result; + }, + + _setOption: function( key, value ) { + if ( key === "direction" ) { + this._removeClass( "ui-controlgroup-" + this.options.direction ); + } + + this._super( key, value ); + if ( key === "disabled" ) { + this._callChildMethod( value ? "disable" : "enable" ); + return; + } + + this.refresh(); + }, + + refresh: function() { + var children, + that = this; + + this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction ); + + if ( this.options.direction === "horizontal" ) { + this._addClass( null, "ui-helper-clearfix" ); + } + this._initWidgets(); + + children = this.childWidgets; + + // We filter here because we need to track all childWidgets not just the visible ones + if ( this.options.onlyVisible ) { + children = children.filter( ":visible" ); + } + + if ( children.length ) { + + // We do this last because we need to make sure all enhancment is done + // before determining first and last + $.each( [ "first", "last" ], function( index, value ) { + var instance = children[ value ]().data( "ui-controlgroup-data" ); + + if ( instance && that[ "_" + instance.widgetName + "Options" ] ) { + var options = that[ "_" + instance.widgetName + "Options" ]( + children.length === 1 ? "only" : value + ); + options.classes = that._resolveClassesValues( options.classes, instance ); + instance.element[ instance.widgetName ]( options ); + } else { + that._updateCornerClass( children[ value ](), value ); + } + } ); + + // Finally call the refresh method on each of the child widgets. + this._callChildMethod( "refresh" ); + } + } + } ); + + /*! + * jQuery UI Checkboxradio 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -8957,258 +9080,254 @@ var widgetsControlgroup = $.widget( "ui.controlgroup", { //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { - version: "1.12.1", - options: { - disabled: null, - label: null, - icon: true, - classes: { - "ui-checkboxradio-label": "ui-corner-all", - "ui-checkboxradio-icon": "ui-corner-all" - } - }, - - _getCreateOptions: function() { - var disabled, labels; - var that = this; - var options = this._super() || {}; - - // We read the type here, because it makes more sense to throw a element type error first, - // rather then the error for lack of a label. Often if its the wrong type, it - // won't have a label (e.g. calling on a div, btn, etc) - this._readType(); - - labels = this.element.labels(); - - // If there are multiple labels, use the last one - this.label = $( labels[ labels.length - 1 ] ); - if ( !this.label.length ) { - $.error( "No label found for checkboxradio widget" ); - } - - this.originalLabel = ""; - - // We need to get the label text but this may also need to make sure it does not contain the - // input itself. - this.label.contents().not( this.element[ 0 ] ).each( function() { - - // The label contents could be text, html, or a mix. We concat each element to get a - // string representation of the label, without the input as part of it. - that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML; - } ); - - // Set the label option if we found label text - if ( this.originalLabel ) { - options.label = this.originalLabel; - } - - disabled = this.element[ 0 ].disabled; - if ( disabled != null ) { - options.disabled = disabled; - } - return options; - }, - - _create: function() { - var checked = this.element[ 0 ].checked; - - this._bindFormResetHandler(); - - if ( this.options.disabled == null ) { - this.options.disabled = this.element[ 0 ].disabled; - } - - this._setOption( "disabled", this.options.disabled ); - this._addClass( "ui-checkboxradio", "ui-helper-hidden-accessible" ); - this._addClass( this.label, "ui-checkboxradio-label", "ui-button ui-widget" ); - - if ( this.type === "radio" ) { - this._addClass( this.label, "ui-checkboxradio-radio-label" ); - } - - if ( this.options.label && this.options.label !== this.originalLabel ) { - this._updateLabel(); - } else if ( this.originalLabel ) { - this.options.label = this.originalLabel; - } - - this._enhance(); - - if ( checked ) { - this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" ); - if ( this.icon ) { - this._addClass( this.icon, null, "ui-state-hover" ); - } - } - - this._on( { - change: "_toggleClasses", - focus: function() { - this._addClass( this.label, null, "ui-state-focus ui-visual-focus" ); - }, - blur: function() { - this._removeClass( this.label, null, "ui-state-focus ui-visual-focus" ); - } - } ); - }, - - _readType: function() { - var nodeName = this.element[ 0 ].nodeName.toLowerCase(); - this.type = this.element[ 0 ].type; - if ( nodeName !== "input" || !/radio|checkbox/.test( this.type ) ) { - $.error( "Can't create checkboxradio on element.nodeName=" + nodeName + - " and element.type=" + this.type ); - } - }, - - // Support jQuery Mobile enhanced option - _enhance: function() { - this._updateIcon( this.element[ 0 ].checked ); - }, - - widget: function() { - return this.label; - }, - - _getRadioGroup: function() { - var group; - var name = this.element[ 0 ].name; - var nameSelector = "input[name='" + $.ui.escapeSelector( name ) + "']"; - - if ( !name ) { - return $( [] ); - } - - if ( this.form.length ) { - group = $( this.form[ 0 ].elements ).filter( nameSelector ); - } else { - - // Not inside a form, check all inputs that also are not inside a form - group = $( nameSelector ).filter( function() { - return $( this ).form().length === 0; - } ); - } - - return group.not( this.element ); - }, - - _toggleClasses: function() { - var checked = this.element[ 0 ].checked; - this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); - - if ( this.options.icon && this.type === "checkbox" ) { - this._toggleClass( this.icon, null, "ui-icon-check ui-state-checked", checked ) - ._toggleClass( this.icon, null, "ui-icon-blank", !checked ); - } - - if ( this.type === "radio" ) { - this._getRadioGroup() - .each( function() { - var instance = $( this ).checkboxradio( "instance" ); - - if ( instance ) { - instance._removeClass( instance.label, - "ui-checkboxradio-checked", "ui-state-active" ); - } - } ); - } - }, - - _destroy: function() { - this._unbindFormResetHandler(); - - if ( this.icon ) { - this.icon.remove(); - this.iconSpace.remove(); - } - }, - - _setOption: function( key, value ) { - - // We don't allow the value to be set to nothing - if ( key === "label" && !value ) { - return; - } - - this._super( key, value ); - - if ( key === "disabled" ) { - this._toggleClass( this.label, null, "ui-state-disabled", value ); - this.element[ 0 ].disabled = value; - - // Don't refresh when setting disabled - return; - } - this.refresh(); - }, - - _updateIcon: function( checked ) { - var toAdd = "ui-icon ui-icon-background "; - - if ( this.options.icon ) { - if ( !this.icon ) { - this.icon = $( "<span>" ); - this.iconSpace = $( "<span> </span>" ); - this._addClass( this.iconSpace, "ui-checkboxradio-icon-space" ); - } - - if ( this.type === "checkbox" ) { - toAdd += checked ? "ui-icon-check ui-state-checked" : "ui-icon-blank"; - this._removeClass( this.icon, null, checked ? "ui-icon-blank" : "ui-icon-check" ); - } else { - toAdd += "ui-icon-blank"; - } - this._addClass( this.icon, "ui-checkboxradio-icon", toAdd ); - if ( !checked ) { - this._removeClass( this.icon, null, "ui-icon-check ui-state-checked" ); - } - this.icon.prependTo( this.label ).after( this.iconSpace ); - } else if ( this.icon !== undefined ) { - this.icon.remove(); - this.iconSpace.remove(); - delete this.icon; - } - }, - - _updateLabel: function() { - - // Remove the contents of the label ( minus the icon, icon space, and input ) - var contents = this.label.contents().not( this.element[ 0 ] ); - if ( this.icon ) { - contents = contents.not( this.icon[ 0 ] ); - } - if ( this.iconSpace ) { - contents = contents.not( this.iconSpace[ 0 ] ); - } - contents.remove(); - - this.label.append( this.options.label ); - }, - - refresh: function() { - var checked = this.element[ 0 ].checked, - isDisabled = this.element[ 0 ].disabled; - - this._updateIcon( checked ); - this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); - if ( this.options.label !== null ) { - this._updateLabel(); - } - - if ( isDisabled !== this.options.disabled ) { - this._setOptions( { "disabled": isDisabled } ); - } - } - -} ] ); - -var widgetsCheckboxradio = $.ui.checkboxradio; - - -/*! - * jQuery UI Button 1.12.1 + $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { + version: "1.13.0", + options: { + disabled: null, + label: null, + icon: true, + classes: { + "ui-checkboxradio-label": "ui-corner-all", + "ui-checkboxradio-icon": "ui-corner-all" + } + }, + + _getCreateOptions: function() { + var disabled, labels; + var that = this; + var options = this._super() || {}; + + // We read the type here, because it makes more sense to throw a element type error first, + // rather then the error for lack of a label. Often if its the wrong type, it + // won't have a label (e.g. calling on a div, btn, etc) + this._readType(); + + labels = this.element.labels(); + + // If there are multiple labels, use the last one + this.label = $( labels[ labels.length - 1 ] ); + if ( !this.label.length ) { + $.error( "No label found for checkboxradio widget" ); + } + + this.originalLabel = ""; + + // We need to get the label text but this may also need to make sure it does not contain the + // input itself. + this.label.contents().not( this.element[ 0 ] ).each( function() { + + // The label contents could be text, html, or a mix. We concat each element to get a + // string representation of the label, without the input as part of it. + that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML; + } ); + + // Set the label option if we found label text + if ( this.originalLabel ) { + options.label = this.originalLabel; + } + + disabled = this.element[ 0 ].disabled; + if ( disabled != null ) { + options.disabled = disabled; + } + return options; + }, + + _create: function() { + var checked = this.element[ 0 ].checked; + + this._bindFormResetHandler(); + + if ( this.options.disabled == null ) { + this.options.disabled = this.element[ 0 ].disabled; + } + + this._setOption( "disabled", this.options.disabled ); + this._addClass( "ui-checkboxradio", "ui-helper-hidden-accessible" ); + this._addClass( this.label, "ui-checkboxradio-label", "ui-button ui-widget" ); + + if ( this.type === "radio" ) { + this._addClass( this.label, "ui-checkboxradio-radio-label" ); + } + + if ( this.options.label && this.options.label !== this.originalLabel ) { + this._updateLabel(); + } else if ( this.originalLabel ) { + this.options.label = this.originalLabel; + } + + this._enhance(); + + if ( checked ) { + this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" ); + } + + this._on( { + change: "_toggleClasses", + focus: function() { + this._addClass( this.label, null, "ui-state-focus ui-visual-focus" ); + }, + blur: function() { + this._removeClass( this.label, null, "ui-state-focus ui-visual-focus" ); + } + } ); + }, + + _readType: function() { + var nodeName = this.element[ 0 ].nodeName.toLowerCase(); + this.type = this.element[ 0 ].type; + if ( nodeName !== "input" || !/radio|checkbox/.test( this.type ) ) { + $.error( "Can't create checkboxradio on element.nodeName=" + nodeName + + " and element.type=" + this.type ); + } + }, + + // Support jQuery Mobile enhanced option + _enhance: function() { + this._updateIcon( this.element[ 0 ].checked ); + }, + + widget: function() { + return this.label; + }, + + _getRadioGroup: function() { + var group; + var name = this.element[ 0 ].name; + var nameSelector = "input[name='" + $.escapeSelector( name ) + "']"; + + if ( !name ) { + return $( [] ); + } + + if ( this.form.length ) { + group = $( this.form[ 0 ].elements ).filter( nameSelector ); + } else { + + // Not inside a form, check all inputs that also are not inside a form + group = $( nameSelector ).filter( function() { + return $( this )._form().length === 0; + } ); + } + + return group.not( this.element ); + }, + + _toggleClasses: function() { + var checked = this.element[ 0 ].checked; + this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); + + if ( this.options.icon && this.type === "checkbox" ) { + this._toggleClass( this.icon, null, "ui-icon-check ui-state-checked", checked ) + ._toggleClass( this.icon, null, "ui-icon-blank", !checked ); + } + + if ( this.type === "radio" ) { + this._getRadioGroup() + .each( function() { + var instance = $( this ).checkboxradio( "instance" ); + + if ( instance ) { + instance._removeClass( instance.label, + "ui-checkboxradio-checked", "ui-state-active" ); + } + } ); + } + }, + + _destroy: function() { + this._unbindFormResetHandler(); + + if ( this.icon ) { + this.icon.remove(); + this.iconSpace.remove(); + } + }, + + _setOption: function( key, value ) { + + // We don't allow the value to be set to nothing + if ( key === "label" && !value ) { + return; + } + + this._super( key, value ); + + if ( key === "disabled" ) { + this._toggleClass( this.label, null, "ui-state-disabled", value ); + this.element[ 0 ].disabled = value; + + // Don't refresh when setting disabled + return; + } + this.refresh(); + }, + + _updateIcon: function( checked ) { + var toAdd = "ui-icon ui-icon-background "; + + if ( this.options.icon ) { + if ( !this.icon ) { + this.icon = $( "<span>" ); + this.iconSpace = $( "<span> </span>" ); + this._addClass( this.iconSpace, "ui-checkboxradio-icon-space" ); + } + + if ( this.type === "checkbox" ) { + toAdd += checked ? "ui-icon-check ui-state-checked" : "ui-icon-blank"; + this._removeClass( this.icon, null, checked ? "ui-icon-blank" : "ui-icon-check" ); + } else { + toAdd += "ui-icon-blank"; + } + this._addClass( this.icon, "ui-checkboxradio-icon", toAdd ); + if ( !checked ) { + this._removeClass( this.icon, null, "ui-icon-check ui-state-checked" ); + } + this.icon.prependTo( this.label ).after( this.iconSpace ); + } else if ( this.icon !== undefined ) { + this.icon.remove(); + this.iconSpace.remove(); + delete this.icon; + } + }, + + _updateLabel: function() { + + // Remove the contents of the label ( minus the icon, icon space, and input ) + var contents = this.label.contents().not( this.element[ 0 ] ); + if ( this.icon ) { + contents = contents.not( this.icon[ 0 ] ); + } + if ( this.iconSpace ) { + contents = contents.not( this.iconSpace[ 0 ] ); + } + contents.remove(); + + this.label.append( this.options.label ); + }, + + refresh: function() { + var checked = this.element[ 0 ].checked, + isDisabled = this.element[ 0 ].disabled; + + this._updateIcon( checked ); + this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); + if ( this.options.label !== null ) { + this._updateLabel(); + } + + if ( isDisabled !== this.options.disabled ) { + this._setOptions( { "disabled": isDisabled } ); + } + } + + } ] ); + + var widgetsCheckboxradio = $.ui.checkboxradio; + + + /*! + * jQuery UI Button 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9226,357 +9345,415 @@ var widgetsCheckboxradio = $.ui.checkboxradio; //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.button", { - version: "1.12.1", - defaultElement: "<button>", - options: { - classes: { - "ui-button": "ui-corner-all" - }, - disabled: null, - icon: null, - iconPosition: "beginning", - label: null, - showLabel: true - }, - - _getCreateOptions: function() { - var disabled, - - // This is to support cases like in jQuery Mobile where the base widget does have - // an implementation of _getCreateOptions - options = this._super() || {}; - - this.isInput = this.element.is( "input" ); - - disabled = this.element[ 0 ].disabled; - if ( disabled != null ) { - options.disabled = disabled; - } - - this.originalLabel = this.isInput ? this.element.val() : this.element.html(); - if ( this.originalLabel ) { - options.label = this.originalLabel; - } - - return options; - }, - - _create: function() { - if ( !this.option.showLabel & !this.options.icon ) { - this.options.showLabel = true; - } - - // We have to check the option again here even though we did in _getCreateOptions, - // because null may have been passed on init which would override what was set in - // _getCreateOptions - if ( this.options.disabled == null ) { - this.options.disabled = this.element[ 0 ].disabled || false; - } - - this.hasTitle = !!this.element.attr( "title" ); - - // Check to see if the label needs to be set or if its already correct - if ( this.options.label && this.options.label !== this.originalLabel ) { - if ( this.isInput ) { - this.element.val( this.options.label ); - } else { - this.element.html( this.options.label ); - } - } - this._addClass( "ui-button", "ui-widget" ); - this._setOption( "disabled", this.options.disabled ); - this._enhance(); - - if ( this.element.is( "a" ) ) { - this._on( { - "keyup": function( event ) { - if ( event.keyCode === $.ui.keyCode.SPACE ) { - event.preventDefault(); - - // Support: PhantomJS <= 1.9, IE 8 Only - // If a native click is available use it so we actually cause navigation - // otherwise just trigger a click event - if ( this.element[ 0 ].click ) { - this.element[ 0 ].click(); - } else { - this.element.trigger( "click" ); - } - } - } - } ); - } - }, - - _enhance: function() { - if ( !this.element.is( "button" ) ) { - this.element.attr( "role", "button" ); - } - - if ( this.options.icon ) { - this._updateIcon( "icon", this.options.icon ); - this._updateTooltip(); - } - }, - - _updateTooltip: function() { - this.title = this.element.attr( "title" ); - - if ( !this.options.showLabel && !this.title ) { - this.element.attr( "title", this.options.label ); - } - }, - - _updateIcon: function( option, value ) { - var icon = option !== "iconPosition", - position = icon ? this.options.iconPosition : value, - displayBlock = position === "top" || position === "bottom"; - - // Create icon - if ( !this.icon ) { - this.icon = $( "<span>" ); - - this._addClass( this.icon, "ui-button-icon", "ui-icon" ); - - if ( !this.options.showLabel ) { - this._addClass( "ui-button-icon-only" ); - } - } else if ( icon ) { - - // If we are updating the icon remove the old icon class - this._removeClass( this.icon, null, this.options.icon ); - } - - // If we are updating the icon add the new icon class - if ( icon ) { - this._addClass( this.icon, null, value ); - } - - this._attachIcon( position ); - - // If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove - // the iconSpace if there is one. - if ( displayBlock ) { - this._addClass( this.icon, null, "ui-widget-icon-block" ); - if ( this.iconSpace ) { - this.iconSpace.remove(); - } - } else { - - // Position is beginning or end so remove the ui-widget-icon-block class and add the - // space if it does not exist - if ( !this.iconSpace ) { - this.iconSpace = $( "<span> </span>" ); - this._addClass( this.iconSpace, "ui-button-icon-space" ); - } - this._removeClass( this.icon, null, "ui-wiget-icon-block" ); - this._attachIconSpace( position ); - } - }, - - _destroy: function() { - this.element.removeAttr( "role" ); - - if ( this.icon ) { - this.icon.remove(); - } - if ( this.iconSpace ) { - this.iconSpace.remove(); - } - if ( !this.hasTitle ) { - this.element.removeAttr( "title" ); - } - }, - - _attachIconSpace: function( iconPosition ) { - this.icon[ /^(?:end|bottom)/.test( iconPosition ) ? "before" : "after" ]( this.iconSpace ); - }, - - _attachIcon: function( iconPosition ) { - this.element[ /^(?:end|bottom)/.test( iconPosition ) ? "append" : "prepend" ]( this.icon ); - }, - - _setOptions: function( options ) { - var newShowLabel = options.showLabel === undefined ? - this.options.showLabel : - options.showLabel, - newIcon = options.icon === undefined ? this.options.icon : options.icon; - - if ( !newShowLabel && !newIcon ) { - options.showLabel = true; - } - this._super( options ); - }, - - _setOption: function( key, value ) { - if ( key === "icon" ) { - if ( value ) { - this._updateIcon( key, value ); - } else if ( this.icon ) { - this.icon.remove(); - if ( this.iconSpace ) { - this.iconSpace.remove(); - } - } - } - - if ( key === "iconPosition" ) { - this._updateIcon( key, value ); - } - - // Make sure we can't end up with a button that has neither text nor icon - if ( key === "showLabel" ) { - this._toggleClass( "ui-button-icon-only", null, !value ); - this._updateTooltip(); - } - - if ( key === "label" ) { - if ( this.isInput ) { - this.element.val( value ); - } else { - - // If there is an icon, append it, else nothing then append the value - // this avoids removal of the icon when setting label text - this.element.html( value ); - if ( this.icon ) { - this._attachIcon( this.options.iconPosition ); - this._attachIconSpace( this.options.iconPosition ); - } - } - } - - this._super( key, value ); - - if ( key === "disabled" ) { - this._toggleClass( null, "ui-state-disabled", value ); - this.element[ 0 ].disabled = value; - if ( value ) { - this.element.blur(); - } - } - }, - - refresh: function() { - - // Make sure to only check disabled if its an element that supports this otherwise - // check for the disabled class to determine state - var isDisabled = this.element.is( "input, button" ) ? - this.element[ 0 ].disabled : this.element.hasClass( "ui-button-disabled" ); - - if ( isDisabled !== this.options.disabled ) { - this._setOptions( { disabled: isDisabled } ); - } - - this._updateTooltip(); - } -} ); + $.widget( "ui.button", { + version: "1.13.0", + defaultElement: "<button>", + options: { + classes: { + "ui-button": "ui-corner-all" + }, + disabled: null, + icon: null, + iconPosition: "beginning", + label: null, + showLabel: true + }, + + _getCreateOptions: function() { + var disabled, + + // This is to support cases like in jQuery Mobile where the base widget does have + // an implementation of _getCreateOptions + options = this._super() || {}; + + this.isInput = this.element.is( "input" ); + + disabled = this.element[ 0 ].disabled; + if ( disabled != null ) { + options.disabled = disabled; + } + + this.originalLabel = this.isInput ? this.element.val() : this.element.html(); + if ( this.originalLabel ) { + options.label = this.originalLabel; + } + + return options; + }, + + _create: function() { + if ( !this.option.showLabel & !this.options.icon ) { + this.options.showLabel = true; + } + + // We have to check the option again here even though we did in _getCreateOptions, + // because null may have been passed on init which would override what was set in + // _getCreateOptions + if ( this.options.disabled == null ) { + this.options.disabled = this.element[ 0 ].disabled || false; + } + + this.hasTitle = !!this.element.attr( "title" ); + + // Check to see if the label needs to be set or if its already correct + if ( this.options.label && this.options.label !== this.originalLabel ) { + if ( this.isInput ) { + this.element.val( this.options.label ); + } else { + this.element.html( this.options.label ); + } + } + this._addClass( "ui-button", "ui-widget" ); + this._setOption( "disabled", this.options.disabled ); + this._enhance(); + + if ( this.element.is( "a" ) ) { + this._on( { + "keyup": function( event ) { + if ( event.keyCode === $.ui.keyCode.SPACE ) { + event.preventDefault(); + + // Support: PhantomJS <= 1.9, IE 8 Only + // If a native click is available use it so we actually cause navigation + // otherwise just trigger a click event + if ( this.element[ 0 ].click ) { + this.element[ 0 ].click(); + } else { + this.element.trigger( "click" ); + } + } + } + } ); + } + }, + + _enhance: function() { + if ( !this.element.is( "button" ) ) { + this.element.attr( "role", "button" ); + } + + if ( this.options.icon ) { + this._updateIcon( "icon", this.options.icon ); + this._updateTooltip(); + } + }, + + _updateTooltip: function() { + this.title = this.element.attr( "title" ); + + if ( !this.options.showLabel && !this.title ) { + this.element.attr( "title", this.options.label ); + } + }, + + _updateIcon: function( option, value ) { + var icon = option !== "iconPosition", + position = icon ? this.options.iconPosition : value, + displayBlock = position === "top" || position === "bottom"; + + // Create icon + if ( !this.icon ) { + this.icon = $( "<span>" ); + + this._addClass( this.icon, "ui-button-icon", "ui-icon" ); + + if ( !this.options.showLabel ) { + this._addClass( "ui-button-icon-only" ); + } + } else if ( icon ) { + + // If we are updating the icon remove the old icon class + this._removeClass( this.icon, null, this.options.icon ); + } + + // If we are updating the icon add the new icon class + if ( icon ) { + this._addClass( this.icon, null, value ); + } + + this._attachIcon( position ); + + // If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove + // the iconSpace if there is one. + if ( displayBlock ) { + this._addClass( this.icon, null, "ui-widget-icon-block" ); + if ( this.iconSpace ) { + this.iconSpace.remove(); + } + } else { + + // Position is beginning or end so remove the ui-widget-icon-block class and add the + // space if it does not exist + if ( !this.iconSpace ) { + this.iconSpace = $( "<span> </span>" ); + this._addClass( this.iconSpace, "ui-button-icon-space" ); + } + this._removeClass( this.icon, null, "ui-wiget-icon-block" ); + this._attachIconSpace( position ); + } + }, + + _destroy: function() { + this.element.removeAttr( "role" ); + + if ( this.icon ) { + this.icon.remove(); + } + if ( this.iconSpace ) { + this.iconSpace.remove(); + } + if ( !this.hasTitle ) { + this.element.removeAttr( "title" ); + } + }, + + _attachIconSpace: function( iconPosition ) { + this.icon[ /^(?:end|bottom)/.test( iconPosition ) ? "before" : "after" ]( this.iconSpace ); + }, + + _attachIcon: function( iconPosition ) { + this.element[ /^(?:end|bottom)/.test( iconPosition ) ? "append" : "prepend" ]( this.icon ); + }, + + _setOptions: function( options ) { + var newShowLabel = options.showLabel === undefined ? + this.options.showLabel : + options.showLabel, + newIcon = options.icon === undefined ? this.options.icon : options.icon; + + if ( !newShowLabel && !newIcon ) { + options.showLabel = true; + } + this._super( options ); + }, + + _setOption: function( key, value ) { + if ( key === "icon" ) { + if ( value ) { + this._updateIcon( key, value ); + } else if ( this.icon ) { + this.icon.remove(); + if ( this.iconSpace ) { + this.iconSpace.remove(); + } + } + } + + if ( key === "iconPosition" ) { + this._updateIcon( key, value ); + } + + // Make sure we can't end up with a button that has neither text nor icon + if ( key === "showLabel" ) { + this._toggleClass( "ui-button-icon-only", null, !value ); + this._updateTooltip(); + } + + if ( key === "label" ) { + if ( this.isInput ) { + this.element.val( value ); + } else { + + // If there is an icon, append it, else nothing then append the value + // this avoids removal of the icon when setting label text + this.element.html( value ); + if ( this.icon ) { + this._attachIcon( this.options.iconPosition ); + this._attachIconSpace( this.options.iconPosition ); + } + } + } + + this._super( key, value ); + + if ( key === "disabled" ) { + this._toggleClass( null, "ui-state-disabled", value ); + this.element[ 0 ].disabled = value; + if ( value ) { + this.element.trigger( "blur" ); + } + } + }, + + refresh: function() { + + // Make sure to only check disabled if its an element that supports this otherwise + // check for the disabled class to determine state + var isDisabled = this.element.is( "input, button" ) ? + this.element[ 0 ].disabled : this.element.hasClass( "ui-button-disabled" ); + + if ( isDisabled !== this.options.disabled ) { + this._setOptions( { disabled: isDisabled } ); + } + + this._updateTooltip(); + } + } ); // DEPRECATED -if ( $.uiBackCompat !== false ) { - - // Text and Icons options - $.widget( "ui.button", $.ui.button, { - options: { - text: true, - icons: { - primary: null, - secondary: null - } - }, - - _create: function() { - if ( this.options.showLabel && !this.options.text ) { - this.options.showLabel = this.options.text; - } - if ( !this.options.showLabel && this.options.text ) { - this.options.text = this.options.showLabel; - } - if ( !this.options.icon && ( this.options.icons.primary || - this.options.icons.secondary ) ) { - if ( this.options.icons.primary ) { - this.options.icon = this.options.icons.primary; - } else { - this.options.icon = this.options.icons.secondary; - this.options.iconPosition = "end"; - } - } else if ( this.options.icon ) { - this.options.icons.primary = this.options.icon; - } - this._super(); - }, - - _setOption: function( key, value ) { - if ( key === "text" ) { - this._super( "showLabel", value ); - return; - } - if ( key === "showLabel" ) { - this.options.text = value; - } - if ( key === "icon" ) { - this.options.icons.primary = value; - } - if ( key === "icons" ) { - if ( value.primary ) { - this._super( "icon", value.primary ); - this._super( "iconPosition", "beginning" ); - } else if ( value.secondary ) { - this._super( "icon", value.secondary ); - this._super( "iconPosition", "end" ); - } - } - this._superApply( arguments ); - } - } ); - - $.fn.button = ( function( orig ) { - return function() { - if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) || - ( this.length && this[ 0 ].tagName === "INPUT" && ( - this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio" - ) ) ) { - return orig.apply( this, arguments ); - } - if ( !$.ui.checkboxradio ) { - $.error( "Checkboxradio widget missing" ); - } - if ( arguments.length === 0 ) { - return this.checkboxradio( { - "icon": false - } ); - } - return this.checkboxradio.apply( this, arguments ); - }; - } )( $.fn.button ); - - $.fn.buttonset = function() { - if ( !$.ui.controlgroup ) { - $.error( "Controlgroup widget missing" ); - } - if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" && arguments[ 2 ] ) { - return this.controlgroup.apply( this, - [ arguments[ 0 ], "items.button", arguments[ 2 ] ] ); - } - if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" ) { - return this.controlgroup.apply( this, [ arguments[ 0 ], "items.button" ] ); - } - if ( typeof arguments[ 0 ] === "object" && arguments[ 0 ].items ) { - arguments[ 0 ].items = { - button: arguments[ 0 ].items - }; - } - return this.controlgroup.apply( this, arguments ); - }; -} - -var widgetsButton = $.ui.button; - - -// jscs:disable maximumLineLength -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ -/*! - * jQuery UI Datepicker 1.12.1 + if ( $.uiBackCompat !== false ) { + + // Text and Icons options + $.widget( "ui.button", $.ui.button, { + options: { + text: true, + icons: { + primary: null, + secondary: null + } + }, + + _create: function() { + if ( this.options.showLabel && !this.options.text ) { + this.options.showLabel = this.options.text; + } + if ( !this.options.showLabel && this.options.text ) { + this.options.text = this.options.showLabel; + } + if ( !this.options.icon && ( this.options.icons.primary || + this.options.icons.secondary ) ) { + if ( this.options.icons.primary ) { + this.options.icon = this.options.icons.primary; + } else { + this.options.icon = this.options.icons.secondary; + this.options.iconPosition = "end"; + } + } else if ( this.options.icon ) { + this.options.icons.primary = this.options.icon; + } + this._super(); + }, + + _setOption: function( key, value ) { + if ( key === "text" ) { + this._super( "showLabel", value ); + return; + } + if ( key === "showLabel" ) { + this.options.text = value; + } + if ( key === "icon" ) { + this.options.icons.primary = value; + } + if ( key === "icons" ) { + if ( value.primary ) { + this._super( "icon", value.primary ); + this._super( "iconPosition", "beginning" ); + } else if ( value.secondary ) { + this._super( "icon", value.secondary ); + this._super( "iconPosition", "end" ); + } + } + this._superApply( arguments ); + } + } ); + + $.fn.button = ( function( orig ) { + return function( options ) { + var isMethodCall = typeof options === "string"; + var args = Array.prototype.slice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? + "button" : + "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on button" + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( typeof instance[ options ] !== "function" || + options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for button" + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? "button" : "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + if ( name === "button" ) { + orig.call( $( this ), options ); + return; + } + + $( this ).checkboxradio( $.extend( { icon: false }, options ) ); + } + } ); + } + + return returnValue; + }; + } )( $.fn.button ); + + $.fn.buttonset = function() { + if ( !$.ui.controlgroup ) { + $.error( "Controlgroup widget missing" ); + } + if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" && arguments[ 2 ] ) { + return this.controlgroup.apply( this, + [ arguments[ 0 ], "items.button", arguments[ 2 ] ] ); + } + if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" ) { + return this.controlgroup.apply( this, [ arguments[ 0 ], "items.button" ] ); + } + if ( typeof arguments[ 0 ] === "object" && arguments[ 0 ].items ) { + arguments[ 0 ].items = { + button: arguments[ 0 ].items + }; + } + return this.controlgroup.apply( this, arguments ); + }; + } + + var widgetsButton = $.ui.button; + + + /* eslint-disable max-len, camelcase */ + /*! + * jQuery UI Datepicker 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9594,302 +9771,328 @@ var widgetsButton = $.ui.button; //>>css.theme: ../../themes/base/theme.css + $.extend( $.ui, { datepicker: { version: "1.13.0" } } ); -$.extend( $.ui, { datepicker: { version: "1.12.1" } } ); + var datepicker_instActive; -var datepicker_instActive; + function datepicker_getZindex( elem ) { + var position, value; + while ( elem.length && elem[ 0 ] !== document ) { -function datepicker_getZindex( elem ) { - var position, value; - while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { - // Ignore z-index if position is set to a value where z-index is ignored by the browser - // This makes behavior of this function consistent across browsers - // WebKit always returns auto if the element is positioned - position = elem.css( "position" ); - if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } - // IE returns 0 when zIndex is not specified - // other browsers return a string - // we ignore the case of nested elements with an explicit value of 0 - // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> - value = parseInt( elem.css( "zIndex" ), 10 ); - if ( !isNaN( value ) && value !== 0 ) { - return value; - } - } - elem = elem.parent(); - } + return 0; + } - return 0; -} -/* Date picker manager. + /* Date picker manager. Use the singleton instance of this class, $.datepicker, to interact with the date picker. Settings for (groups of) date pickers are maintained in an instance object, allowing multiple different settings on the same page. */ -function Datepicker() { - this._curInst = null; // The current instance in use - this._keyEvent = false; // If the last event was a key event - this._disabledInputs = []; // List of date picker inputs that have been disabled - this._datepickerShowing = false; // True if the popup picker is showing , false if not - this._inDialog = false; // True if showing within a "dialog", false if not - this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division - this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class - this._appendClass = "ui-datepicker-append"; // The name of the append marker class - this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class - this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class - this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class - this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class - this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class - this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class - this.regional = []; // Available regional settings, indexed by language code - this.regional[ "" ] = { // Default regional settings - closeText: "Done", // Display text for close link - prevText: "Prev", // Display text for previous month link - nextText: "Next", // Display text for next month link - currentText: "Today", // Display text for current month link - monthNames: [ "January","February","March","April","May","June", - "July","August","September","October","November","December" ], // Names of months for drop-down and formatting - monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting - dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting - dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting - dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday - weekHeader: "Wk", // Column header for week of the year - dateFormat: "mm/dd/yy", // See format options on parseDate - firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... - isRTL: false, // True if right-to-left language, false if left-to-right - showMonthAfterYear: false, // True if the year select precedes month, false for month then year - yearSuffix: "" // Additional text to append to the year in the month headers - }; - this._defaults = { // Global defaults for all the date picker instances - showOn: "focus", // "focus" for popup on focus, - // "button" for trigger button, or "both" for either - showAnim: "fadeIn", // Name of jQuery animation for popup - showOptions: {}, // Options for enhanced animations - defaultDate: null, // Used when field is blank: actual date, - // +/-number for offset from today, null for today - appendText: "", // Display text following the input box, e.g. showing the format - buttonText: "...", // Text for trigger button - buttonImage: "", // URL for trigger button image - buttonImageOnly: false, // True if the image appears alone, false if it appears on a button - hideIfNoPrevNext: false, // True to hide next/previous month links - // if not applicable, false to just disable them - navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links - gotoCurrent: false, // True if today link goes back to current selection instead - changeMonth: false, // True if month can be selected directly, false if only prev/next - changeYear: false, // True if year can be selected directly, false if only prev/next - yearRange: "c-10:c+10", // Range of years to display in drop-down, - // either relative to today's year (-nn:+nn), relative to currently displayed year - // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) - showOtherMonths: false, // True to show dates in other months, false to leave blank - selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable - showWeek: false, // True to show week of the year, false to not show it - calculateWeek: this.iso8601Week, // How to calculate the week of the year, - // takes a Date and returns the number of the week for it - shortYearCutoff: "+10", // Short year values < this are in the current century, - // > this are in the previous century, - // string value starting with "+" for current year + value - minDate: null, // The earliest selectable date, or null for no limit - maxDate: null, // The latest selectable date, or null for no limit - duration: "fast", // Duration of display/closure - beforeShowDay: null, // Function that takes a date and returns an array with - // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", - // [2] = cell title (optional), e.g. $.datepicker.noWeekends - beforeShow: null, // Function that takes an input field and - // returns a set of custom settings for the date picker - onSelect: null, // Define a callback function when a date is selected - onChangeMonthYear: null, // Define a callback function when the month or year is changed - onClose: null, // Define a callback function when the datepicker is closed - numberOfMonths: 1, // Number of months to show at a time - showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) - stepMonths: 1, // Number of months to step back/forward - stepBigMonths: 12, // Number of months to step back/forward for the big links - altField: "", // Selector for an alternate field to store selected dates into - altFormat: "", // The date format to use for the alternate field - constrainInput: true, // The input is constrained by the current date format - showButtonPanel: false, // True to show button panel, false to not show it - autoSize: false, // True to size the input for the date format, false to leave as is - disabled: false // The initial disabled state - }; - $.extend( this._defaults, this.regional[ "" ] ); - this.regional.en = $.extend( true, {}, this.regional[ "" ] ); - this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); - this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ); -} - -$.extend( Datepicker.prototype, { - /* Class name added to elements to indicate already configured with a date picker. */ - markerClassName: "hasDatepicker", - - //Keep track of the maximum number of rows displayed (see #7043) - maxRows: 4, - - // TODO rename to "widget" when switching to widget factory - _widgetDatepicker: function() { - return this.dpDiv; - }, - - /* Override the default settings for all instances of the date picker. + function Datepicker() { + this._curInst = null; // The current instance in use + this._keyEvent = false; // If the last event was a key event + this._disabledInputs = []; // List of date picker inputs that have been disabled + this._datepickerShowing = false; // True if the popup picker is showing , false if not + this._inDialog = false; // True if showing within a "dialog", false if not + this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division + this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class + this._appendClass = "ui-datepicker-append"; // The name of the append marker class + this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class + this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class + this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class + this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class + this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class + this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class + this.regional = []; // Available regional settings, indexed by language code + this.regional[ "" ] = { // Default regional settings + closeText: "Done", // Display text for close link + prevText: "Prev", // Display text for previous month link + nextText: "Next", // Display text for next month link + currentText: "Today", // Display text for current month link + monthNames: [ "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" ], // Names of months for drop-down and formatting + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting + dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting + dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting + dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], // Column headings for days starting at Sunday + weekHeader: "Wk", // Column header for week of the year + dateFormat: "mm/dd/yy", // See format options on parseDate + firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... + isRTL: false, // True if right-to-left language, false if left-to-right + showMonthAfterYear: false, // True if the year select precedes month, false for month then year + yearSuffix: "", // Additional text to append to the year in the month headers, + selectMonthLabel: "Select month", // Invisible label for month selector + selectYearLabel: "Select year" // Invisible label for year selector + }; + this._defaults = { // Global defaults for all the date picker instances + showOn: "focus", // "focus" for popup on focus, + // "button" for trigger button, or "both" for either + showAnim: "fadeIn", // Name of jQuery animation for popup + showOptions: {}, // Options for enhanced animations + defaultDate: null, // Used when field is blank: actual date, + // +/-number for offset from today, null for today + appendText: "", // Display text following the input box, e.g. showing the format + buttonText: "...", // Text for trigger button + buttonImage: "", // URL for trigger button image + buttonImageOnly: false, // True if the image appears alone, false if it appears on a button + hideIfNoPrevNext: false, // True to hide next/previous month links + // if not applicable, false to just disable them + navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links + gotoCurrent: false, // True if today link goes back to current selection instead + changeMonth: false, // True if month can be selected directly, false if only prev/next + changeYear: false, // True if year can be selected directly, false if only prev/next + yearRange: "c-10:c+10", // Range of years to display in drop-down, + // either relative to today's year (-nn:+nn), relative to currently displayed year + // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) + showOtherMonths: false, // True to show dates in other months, false to leave blank + selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable + showWeek: false, // True to show week of the year, false to not show it + calculateWeek: this.iso8601Week, // How to calculate the week of the year, + // takes a Date and returns the number of the week for it + shortYearCutoff: "+10", // Short year values < this are in the current century, + // > this are in the previous century, + // string value starting with "+" for current year + value + minDate: null, // The earliest selectable date, or null for no limit + maxDate: null, // The latest selectable date, or null for no limit + duration: "fast", // Duration of display/closure + beforeShowDay: null, // Function that takes a date and returns an array with + // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", + // [2] = cell title (optional), e.g. $.datepicker.noWeekends + beforeShow: null, // Function that takes an input field and + // returns a set of custom settings for the date picker + onSelect: null, // Define a callback function when a date is selected + onChangeMonthYear: null, // Define a callback function when the month or year is changed + onClose: null, // Define a callback function when the datepicker is closed + onUpdateDatepicker: null, // Define a callback function when the datepicker is updated + numberOfMonths: 1, // Number of months to show at a time + showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) + stepMonths: 1, // Number of months to step back/forward + stepBigMonths: 12, // Number of months to step back/forward for the big links + altField: "", // Selector for an alternate field to store selected dates into + altFormat: "", // The date format to use for the alternate field + constrainInput: true, // The input is constrained by the current date format + showButtonPanel: false, // True to show button panel, false to not show it + autoSize: false, // True to size the input for the date format, false to leave as is + disabled: false // The initial disabled state + }; + $.extend( this._defaults, this.regional[ "" ] ); + this.regional.en = $.extend( true, {}, this.regional[ "" ] ); + this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); + this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ); + } + + $.extend( Datepicker.prototype, { + + /* Class name added to elements to indicate already configured with a date picker. */ + markerClassName: "hasDatepicker", + + //Keep track of the maximum number of rows displayed (see #7043) + maxRows: 4, + + // TODO rename to "widget" when switching to widget factory + _widgetDatepicker: function() { + return this.dpDiv; + }, + + /* Override the default settings for all instances of the date picker. * @param settings object - the new settings to use as defaults (anonymous object) * @return the manager object */ - setDefaults: function( settings ) { - datepicker_extendRemove( this._defaults, settings || {} ); - return this; - }, + setDefaults: function( settings ) { + datepicker_extendRemove( this._defaults, settings || {} ); + return this; + }, - /* Attach the date picker to a jQuery selection. + /* Attach the date picker to a jQuery selection. * @param target element - the target input field or division or span * @param settings object - the new settings to use for this date picker instance (anonymous) */ - _attachDatepicker: function( target, settings ) { - var nodeName, inline, inst; - nodeName = target.nodeName.toLowerCase(); - inline = ( nodeName === "div" || nodeName === "span" ); - if ( !target.id ) { - this.uuid += 1; - target.id = "dp" + this.uuid; - } - inst = this._newInst( $( target ), inline ); - inst.settings = $.extend( {}, settings || {} ); - if ( nodeName === "input" ) { - this._connectDatepicker( target, inst ); - } else if ( inline ) { - this._inlineDatepicker( target, inst ); - } - }, - - /* Create a new instance object. */ - _newInst: function( target, inline ) { - var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars - return { id: id, input: target, // associated target - selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection - drawMonth: 0, drawYear: 0, // month being drawn - inline: inline, // is datepicker inline or not - dpDiv: ( !inline ? this.dpDiv : // presentation div - datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) }; - }, - - /* Attach the date picker to an input field. */ - _connectDatepicker: function( target, inst ) { - var input = $( target ); - inst.append = $( [] ); - inst.trigger = $( [] ); - if ( input.hasClass( this.markerClassName ) ) { - return; - } - this._attachments( input, inst ); - input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ). - on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp ); - this._autoSize( inst ); - $.data( target, "datepicker", inst ); - - //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) - if ( inst.settings.disabled ) { - this._disableDatepicker( target ); - } - }, - - /* Make attachments based on settings. */ - _attachments: function( input, inst ) { - var showOn, buttonText, buttonImage, - appendText = this._get( inst, "appendText" ), - isRTL = this._get( inst, "isRTL" ); - - if ( inst.append ) { - inst.append.remove(); - } - if ( appendText ) { - inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" ); - input[ isRTL ? "before" : "after" ]( inst.append ); - } - - input.off( "focus", this._showDatepicker ); - - if ( inst.trigger ) { - inst.trigger.remove(); - } - - showOn = this._get( inst, "showOn" ); - if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field - input.on( "focus", this._showDatepicker ); - } - if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked - buttonText = this._get( inst, "buttonText" ); - buttonImage = this._get( inst, "buttonImage" ); - inst.trigger = $( this._get( inst, "buttonImageOnly" ) ? - $( "<img/>" ).addClass( this._triggerClass ). - attr( { src: buttonImage, alt: buttonText, title: buttonText } ) : - $( "<button type='button'></button>" ).addClass( this._triggerClass ). - html( !buttonImage ? buttonText : $( "<img/>" ).attr( - { src:buttonImage, alt:buttonText, title:buttonText } ) ) ); - input[ isRTL ? "before" : "after" ]( inst.trigger ); - inst.trigger.on( "click", function() { - if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { - $.datepicker._hideDatepicker(); - } else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) { - $.datepicker._hideDatepicker(); - $.datepicker._showDatepicker( input[ 0 ] ); - } else { - $.datepicker._showDatepicker( input[ 0 ] ); - } - return false; - } ); - } - }, - - /* Apply the maximum length for the date format. */ - _autoSize: function( inst ) { - if ( this._get( inst, "autoSize" ) && !inst.inline ) { - var findMax, max, maxI, i, - date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits - dateFormat = this._get( inst, "dateFormat" ); - - if ( dateFormat.match( /[DM]/ ) ) { - findMax = function( names ) { - max = 0; - maxI = 0; - for ( i = 0; i < names.length; i++ ) { - if ( names[ i ].length > max ) { - max = names[ i ].length; - maxI = i; - } - } - return maxI; - }; - date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ? - "monthNames" : "monthNamesShort" ) ) ) ); - date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ? - "dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() ); - } - inst.input.attr( "size", this._formatDate( inst, date ).length ); - } - }, - - /* Attach an inline date picker to a div. */ - _inlineDatepicker: function( target, inst ) { - var divSpan = $( target ); - if ( divSpan.hasClass( this.markerClassName ) ) { - return; - } - divSpan.addClass( this.markerClassName ).append( inst.dpDiv ); - $.data( target, "datepicker", inst ); - this._setDate( inst, this._getDefaultDate( inst ), true ); - this._updateDatepicker( inst ); - this._updateAlternate( inst ); - - //If disabled option is true, disable the datepicker before showing it (see ticket #5665) - if ( inst.settings.disabled ) { - this._disableDatepicker( target ); - } - - // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements - // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height - inst.dpDiv.css( "display", "block" ); - }, - - /* Pop-up the date picker in a "dialog" box. + _attachDatepicker: function( target, settings ) { + var nodeName, inline, inst; + nodeName = target.nodeName.toLowerCase(); + inline = ( nodeName === "div" || nodeName === "span" ); + if ( !target.id ) { + this.uuid += 1; + target.id = "dp" + this.uuid; + } + inst = this._newInst( $( target ), inline ); + inst.settings = $.extend( {}, settings || {} ); + if ( nodeName === "input" ) { + this._connectDatepicker( target, inst ); + } else if ( inline ) { + this._inlineDatepicker( target, inst ); + } + }, + + /* Create a new instance object. */ + _newInst: function( target, inline ) { + var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars + return { id: id, input: target, // associated target + selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection + drawMonth: 0, drawYear: 0, // month being drawn + inline: inline, // is datepicker inline or not + dpDiv: ( !inline ? this.dpDiv : // presentation div + datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) }; + }, + + /* Attach the date picker to an input field. */ + _connectDatepicker: function( target, inst ) { + var input = $( target ); + inst.append = $( [] ); + inst.trigger = $( [] ); + if ( input.hasClass( this.markerClassName ) ) { + return; + } + this._attachments( input, inst ); + input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ). + on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp ); + this._autoSize( inst ); + $.data( target, "datepicker", inst ); + + //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) + if ( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + }, + + /* Make attachments based on settings. */ + _attachments: function( input, inst ) { + var showOn, buttonText, buttonImage, + appendText = this._get( inst, "appendText" ), + isRTL = this._get( inst, "isRTL" ); + + if ( inst.append ) { + inst.append.remove(); + } + if ( appendText ) { + inst.append = $( "<span>" ) + .addClass( this._appendClass ) + .text( appendText ); + input[ isRTL ? "before" : "after" ]( inst.append ); + } + + input.off( "focus", this._showDatepicker ); + + if ( inst.trigger ) { + inst.trigger.remove(); + } + + showOn = this._get( inst, "showOn" ); + if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field + input.on( "focus", this._showDatepicker ); + } + if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked + buttonText = this._get( inst, "buttonText" ); + buttonImage = this._get( inst, "buttonImage" ); + + if ( this._get( inst, "buttonImageOnly" ) ) { + inst.trigger = $( "<img>" ) + .addClass( this._triggerClass ) + .attr( { + src: buttonImage, + alt: buttonText, + title: buttonText + } ); + } else { + inst.trigger = $( "<button type='button'>" ) + .addClass( this._triggerClass ); + if ( buttonImage ) { + inst.trigger.html( + $( "<img>" ) + .attr( { + src: buttonImage, + alt: buttonText, + title: buttonText + } ) + ); + } else { + inst.trigger.text( buttonText ); + } + } + + input[ isRTL ? "before" : "after" ]( inst.trigger ); + inst.trigger.on( "click", function() { + if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { + $.datepicker._hideDatepicker(); + } else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker( input[ 0 ] ); + } else { + $.datepicker._showDatepicker( input[ 0 ] ); + } + return false; + } ); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function( inst ) { + if ( this._get( inst, "autoSize" ) && !inst.inline ) { + var findMax, max, maxI, i, + date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits + dateFormat = this._get( inst, "dateFormat" ); + + if ( dateFormat.match( /[DM]/ ) ) { + findMax = function( names ) { + max = 0; + maxI = 0; + for ( i = 0; i < names.length; i++ ) { + if ( names[ i ].length > max ) { + max = names[ i ].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ? + "monthNames" : "monthNamesShort" ) ) ) ); + date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ? + "dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() ); + } + inst.input.attr( "size", this._formatDate( inst, date ).length ); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function( target, inst ) { + var divSpan = $( target ); + if ( divSpan.hasClass( this.markerClassName ) ) { + return; + } + divSpan.addClass( this.markerClassName ).append( inst.dpDiv ); + $.data( target, "datepicker", inst ); + this._setDate( inst, this._getDefaultDate( inst ), true ); + this._updateDatepicker( inst ); + this._updateAlternate( inst ); + + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if ( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. * @param input element - ignored * @param date string or Date - the initial date to display * @param onSelect function - the function to call when a date is selected @@ -9899,167 +10102,179 @@ $.extend( Datepicker.prototype, { * leave empty for default (screen centre) * @return the manager object */ - _dialogDatepicker: function( input, date, onSelect, settings, pos ) { - var id, browserWidth, browserHeight, scrollX, scrollY, - inst = this._dialogInst; // internal instance - - if ( !inst ) { - this.uuid += 1; - id = "dp" + this.uuid; - this._dialogInput = $( "<input type='text' id='" + id + - "' style='position: absolute; top: -100px; width: 0px;'/>" ); - this._dialogInput.on( "keydown", this._doKeyDown ); - $( "body" ).append( this._dialogInput ); - inst = this._dialogInst = this._newInst( this._dialogInput, false ); - inst.settings = {}; - $.data( this._dialogInput[ 0 ], "datepicker", inst ); - } - datepicker_extendRemove( inst.settings, settings || {} ); - date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date ); - this._dialogInput.val( date ); - - this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null ); - if ( !this._pos ) { - browserWidth = document.documentElement.clientWidth; - browserHeight = document.documentElement.clientHeight; - scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; - scrollY = document.documentElement.scrollTop || document.body.scrollTop; - this._pos = // should use actual width/height below - [ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ]; - } - - // Move input on screen for focus, but hidden behind dialog - this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" ); - inst.settings.onSelect = onSelect; - this._inDialog = true; - this.dpDiv.addClass( this._dialogClass ); - this._showDatepicker( this._dialogInput[ 0 ] ); - if ( $.blockUI ) { - $.blockUI( this.dpDiv ); - } - $.data( this._dialogInput[ 0 ], "datepicker", inst ); - return this; - }, - - /* Detach a datepicker from its control. + _dialogDatepicker: function( input, date, onSelect, settings, pos ) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if ( !inst ) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $( "<input type='text' id='" + id + + "' style='position: absolute; top: -100px; width: 0px;'/>" ); + this._dialogInput.on( "keydown", this._doKeyDown ); + $( "body" ).append( this._dialogInput ); + inst = this._dialogInst = this._newInst( this._dialogInput, false ); + inst.settings = {}; + $.data( this._dialogInput[ 0 ], "datepicker", inst ); + } + datepicker_extendRemove( inst.settings, settings || {} ); + date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date ); + this._dialogInput.val( date ); + + this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null ); + if ( !this._pos ) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ]; + } + + // Move input on screen for focus, but hidden behind dialog + this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" ); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass( this._dialogClass ); + this._showDatepicker( this._dialogInput[ 0 ] ); + if ( $.blockUI ) { + $.blockUI( this.dpDiv ); + } + $.data( this._dialogInput[ 0 ], "datepicker", inst ); + return this; + }, + + /* Detach a datepicker from its control. * @param target element - the target input field or division or span */ - _destroyDatepicker: function( target ) { - var nodeName, - $target = $( target ), - inst = $.data( target, "datepicker" ); - - if ( !$target.hasClass( this.markerClassName ) ) { - return; - } - - nodeName = target.nodeName.toLowerCase(); - $.removeData( target, "datepicker" ); - if ( nodeName === "input" ) { - inst.append.remove(); - inst.trigger.remove(); - $target.removeClass( this.markerClassName ). - off( "focus", this._showDatepicker ). - off( "keydown", this._doKeyDown ). - off( "keypress", this._doKeyPress ). - off( "keyup", this._doKeyUp ); - } else if ( nodeName === "div" || nodeName === "span" ) { - $target.removeClass( this.markerClassName ).empty(); - } - - if ( datepicker_instActive === inst ) { - datepicker_instActive = null; - } - }, - - /* Enable the date picker to a jQuery selection. + _destroyDatepicker: function( target ) { + var nodeName, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData( target, "datepicker" ); + if ( nodeName === "input" ) { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass( this.markerClassName ). + off( "focus", this._showDatepicker ). + off( "keydown", this._doKeyDown ). + off( "keypress", this._doKeyPress ). + off( "keyup", this._doKeyUp ); + } else if ( nodeName === "div" || nodeName === "span" ) { + $target.removeClass( this.markerClassName ).empty(); + } + + if ( datepicker_instActive === inst ) { + datepicker_instActive = null; + this._curInst = null; + } + }, + + /* Enable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ - _enableDatepicker: function( target ) { - var nodeName, inline, - $target = $( target ), - inst = $.data( target, "datepicker" ); - - if ( !$target.hasClass( this.markerClassName ) ) { - return; - } - - nodeName = target.nodeName.toLowerCase(); - if ( nodeName === "input" ) { - target.disabled = false; - inst.trigger.filter( "button" ). - each( function() { this.disabled = false; } ).end(). - filter( "img" ).css( { opacity: "1.0", cursor: "" } ); - } else if ( nodeName === "div" || nodeName === "span" ) { - inline = $target.children( "." + this._inlineClass ); - inline.children().removeClass( "ui-state-disabled" ); - inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). - prop( "disabled", false ); - } - this._disabledInputs = $.map( this._disabledInputs, - function( value ) { return ( value === target ? null : value ); } ); // delete entry - }, - - /* Disable the date picker to a jQuery selection. + _enableDatepicker: function( target ) { + var nodeName, inline, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if ( nodeName === "input" ) { + target.disabled = false; + inst.trigger.filter( "button" ). + each( function() { + this.disabled = false; + } ).end(). + filter( "img" ).css( { opacity: "1.0", cursor: "" } ); + } else if ( nodeName === "div" || nodeName === "span" ) { + inline = $target.children( "." + this._inlineClass ); + inline.children().removeClass( "ui-state-disabled" ); + inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). + prop( "disabled", false ); + } + this._disabledInputs = $.map( this._disabledInputs, + + // Delete entry + function( value ) { + return ( value === target ? null : value ); + } ); + }, + + /* Disable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ - _disableDatepicker: function( target ) { - var nodeName, inline, - $target = $( target ), - inst = $.data( target, "datepicker" ); - - if ( !$target.hasClass( this.markerClassName ) ) { - return; - } - - nodeName = target.nodeName.toLowerCase(); - if ( nodeName === "input" ) { - target.disabled = true; - inst.trigger.filter( "button" ). - each( function() { this.disabled = true; } ).end(). - filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); - } else if ( nodeName === "div" || nodeName === "span" ) { - inline = $target.children( "." + this._inlineClass ); - inline.children().addClass( "ui-state-disabled" ); - inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). - prop( "disabled", true ); - } - this._disabledInputs = $.map( this._disabledInputs, - function( value ) { return ( value === target ? null : value ); } ); // delete entry - this._disabledInputs[ this._disabledInputs.length ] = target; - }, - - /* Is the first field in a jQuery collection disabled as a datepicker? + _disableDatepicker: function( target ) { + var nodeName, inline, + $target = $( target ), + inst = $.data( target, "datepicker" ); + + if ( !$target.hasClass( this.markerClassName ) ) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if ( nodeName === "input" ) { + target.disabled = true; + inst.trigger.filter( "button" ). + each( function() { + this.disabled = true; + } ).end(). + filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); + } else if ( nodeName === "div" || nodeName === "span" ) { + inline = $target.children( "." + this._inlineClass ); + inline.children().addClass( "ui-state-disabled" ); + inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). + prop( "disabled", true ); + } + this._disabledInputs = $.map( this._disabledInputs, + + // Delete entry + function( value ) { + return ( value === target ? null : value ); + } ); + this._disabledInputs[ this._disabledInputs.length ] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? * @param target element - the target input field or division or span * @return boolean - true if disabled, false if enabled */ - _isDisabledDatepicker: function( target ) { - if ( !target ) { - return false; - } - for ( var i = 0; i < this._disabledInputs.length; i++ ) { - if ( this._disabledInputs[ i ] === target ) { - return true; - } - } - return false; - }, - - /* Retrieve the instance data for the target control. + _isDisabledDatepicker: function( target ) { + if ( !target ) { + return false; + } + for ( var i = 0; i < this._disabledInputs.length; i++ ) { + if ( this._disabledInputs[ i ] === target ) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. * @param target element - the target input field or division or span * @return object - the associated instance data * @throws error if a jQuery problem getting data */ - _getInst: function( target ) { - try { - return $.data( target, "datepicker" ); - } - catch ( err ) { - throw "Missing instance data for this datepicker"; - } - }, - - /* Update or retrieve the settings for a date picker attached to an input field or division. + _getInst: function( target ) { + try { + return $.data( target, "datepicker" ); + } catch ( err ) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. * @param target element - the target input field or division or span * @param name object - the new settings to update or * string - the name of the setting to change or retrieve, @@ -10068,620 +10283,622 @@ $.extend( Datepicker.prototype, { * @param value any - the new value for the setting * (omit if above is an object or to retrieve a value) */ - _optionDatepicker: function( target, name, value ) { - var settings, date, minDate, maxDate, - inst = this._getInst( target ); - - if ( arguments.length === 2 && typeof name === "string" ) { - return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) : - ( inst ? ( name === "all" ? $.extend( {}, inst.settings ) : - this._get( inst, name ) ) : null ) ); - } - - settings = name || {}; - if ( typeof name === "string" ) { - settings = {}; - settings[ name ] = value; - } - - if ( inst ) { - if ( this._curInst === inst ) { - this._hideDatepicker(); - } - - date = this._getDateDatepicker( target, true ); - minDate = this._getMinMaxDate( inst, "min" ); - maxDate = this._getMinMaxDate( inst, "max" ); - datepicker_extendRemove( inst.settings, settings ); - - // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided - if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) { - inst.settings.minDate = this._formatDate( inst, minDate ); - } - if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) { - inst.settings.maxDate = this._formatDate( inst, maxDate ); - } - if ( "disabled" in settings ) { - if ( settings.disabled ) { - this._disableDatepicker( target ); - } else { - this._enableDatepicker( target ); - } - } - this._attachments( $( target ), inst ); - this._autoSize( inst ); - this._setDate( inst, date ); - this._updateAlternate( inst ); - this._updateDatepicker( inst ); - } - }, - - // Change method deprecated - _changeDatepicker: function( target, name, value ) { - this._optionDatepicker( target, name, value ); - }, - - /* Redraw the date picker attached to an input field or division. + _optionDatepicker: function( target, name, value ) { + var settings, date, minDate, maxDate, + inst = this._getInst( target ); + + if ( arguments.length === 2 && typeof name === "string" ) { + return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) : + ( inst ? ( name === "all" ? $.extend( {}, inst.settings ) : + this._get( inst, name ) ) : null ) ); + } + + settings = name || {}; + if ( typeof name === "string" ) { + settings = {}; + settings[ name ] = value; + } + + if ( inst ) { + if ( this._curInst === inst ) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker( target, true ); + minDate = this._getMinMaxDate( inst, "min" ); + maxDate = this._getMinMaxDate( inst, "max" ); + datepicker_extendRemove( inst.settings, settings ); + + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) { + inst.settings.minDate = this._formatDate( inst, minDate ); + } + if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) { + inst.settings.maxDate = this._formatDate( inst, maxDate ); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker( target ); + } else { + this._enableDatepicker( target ); + } + } + this._attachments( $( target ), inst ); + this._autoSize( inst ); + this._setDate( inst, date ); + this._updateAlternate( inst ); + this._updateDatepicker( inst ); + } + }, + + // Change method deprecated + _changeDatepicker: function( target, name, value ) { + this._optionDatepicker( target, name, value ); + }, + + /* Redraw the date picker attached to an input field or division. * @param target element - the target input field or division or span */ - _refreshDatepicker: function( target ) { - var inst = this._getInst( target ); - if ( inst ) { - this._updateDatepicker( inst ); - } - }, - - /* Set the dates for a jQuery selection. + _refreshDatepicker: function( target ) { + var inst = this._getInst( target ); + if ( inst ) { + this._updateDatepicker( inst ); + } + }, + + /* Set the dates for a jQuery selection. * @param target element - the target input field or division or span * @param date Date - the new date */ - _setDateDatepicker: function( target, date ) { - var inst = this._getInst( target ); - if ( inst ) { - this._setDate( inst, date ); - this._updateDatepicker( inst ); - this._updateAlternate( inst ); - } - }, - - /* Get the date(s) for the first entry in a jQuery selection. + _setDateDatepicker: function( target, date ) { + var inst = this._getInst( target ); + if ( inst ) { + this._setDate( inst, date ); + this._updateDatepicker( inst ); + this._updateAlternate( inst ); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. * @param target element - the target input field or division or span * @param noDefault boolean - true if no default date is to be used * @return Date - the current date */ - _getDateDatepicker: function( target, noDefault ) { - var inst = this._getInst( target ); - if ( inst && !inst.inline ) { - this._setDateFromField( inst, noDefault ); - } - return ( inst ? this._getDate( inst ) : null ); - }, - - /* Handle keystrokes. */ - _doKeyDown: function( event ) { - var onSelect, dateStr, sel, - inst = $.datepicker._getInst( event.target ), - handled = true, - isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" ); - - inst._keyEvent = true; - if ( $.datepicker._datepickerShowing ) { - switch ( event.keyCode ) { - case 9: $.datepicker._hideDatepicker(); - handled = false; - break; // hide on tab out - case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." + - $.datepicker._currentClass + ")", inst.dpDiv ); - if ( sel[ 0 ] ) { - $.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] ); - } - - onSelect = $.datepicker._get( inst, "onSelect" ); - if ( onSelect ) { - dateStr = $.datepicker._formatDate( inst ); - - // Trigger custom callback - onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); - } else { - $.datepicker._hideDatepicker(); - } - - return false; // don't submit the form - case 27: $.datepicker._hideDatepicker(); - break; // hide on escape - case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? - -$.datepicker._get( inst, "stepBigMonths" ) : - -$.datepicker._get( inst, "stepMonths" ) ), "M" ); - break; // previous month/year on page up/+ ctrl - case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? - +$.datepicker._get( inst, "stepBigMonths" ) : - +$.datepicker._get( inst, "stepMonths" ) ), "M" ); - break; // next month/year on page down/+ ctrl - case 35: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._clearDate( event.target ); - } - handled = event.ctrlKey || event.metaKey; - break; // clear on ctrl or command +end - case 36: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._gotoToday( event.target ); - } - handled = event.ctrlKey || event.metaKey; - break; // current on ctrl or command +home - case 37: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" ); - } - handled = event.ctrlKey || event.metaKey; - - // -1 day on ctrl or command +left - if ( event.originalEvent.altKey ) { - $.datepicker._adjustDate( event.target, ( event.ctrlKey ? - -$.datepicker._get( inst, "stepBigMonths" ) : - -$.datepicker._get( inst, "stepMonths" ) ), "M" ); - } - - // next month/year on alt +left on Mac - break; - case 38: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._adjustDate( event.target, -7, "D" ); - } - handled = event.ctrlKey || event.metaKey; - break; // -1 week on ctrl or command +up - case 39: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" ); - } - handled = event.ctrlKey || event.metaKey; - - // +1 day on ctrl or command +right - if ( event.originalEvent.altKey ) { - $.datepicker._adjustDate( event.target, ( event.ctrlKey ? - +$.datepicker._get( inst, "stepBigMonths" ) : - +$.datepicker._get( inst, "stepMonths" ) ), "M" ); - } - - // next month/year on alt +right - break; - case 40: if ( event.ctrlKey || event.metaKey ) { - $.datepicker._adjustDate( event.target, +7, "D" ); - } - handled = event.ctrlKey || event.metaKey; - break; // +1 week on ctrl or command +down - default: handled = false; - } - } else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home - $.datepicker._showDatepicker( this ); - } else { - handled = false; - } - - if ( handled ) { - event.preventDefault(); - event.stopPropagation(); - } - }, - - /* Filter entered characters - based on date format. */ - _doKeyPress: function( event ) { - var chars, chr, - inst = $.datepicker._getInst( event.target ); - - if ( $.datepicker._get( inst, "constrainInput" ) ) { - chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) ); - chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode ); - return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 ); - } - }, - - /* Synchronise manual entry and field/alternate field. */ - _doKeyUp: function( event ) { - var date, - inst = $.datepicker._getInst( event.target ); - - if ( inst.input.val() !== inst.lastVal ) { - try { - date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), - ( inst.input ? inst.input.val() : null ), - $.datepicker._getFormatConfig( inst ) ); - - if ( date ) { // only if valid - $.datepicker._setDateFromField( inst ); - $.datepicker._updateAlternate( inst ); - $.datepicker._updateDatepicker( inst ); - } - } - catch ( err ) { - } - } - return true; - }, - - /* Pop-up the date picker for a given input field. + _getDateDatepicker: function( target, noDefault ) { + var inst = this._getInst( target ); + if ( inst && !inst.inline ) { + this._setDateFromField( inst, noDefault ); + } + return ( inst ? this._getDate( inst ) : null ); + }, + + /* Handle keystrokes. */ + _doKeyDown: function( event ) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst( event.target ), + handled = true, + isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" ); + + inst._keyEvent = true; + if ( $.datepicker._datepickerShowing ) { + switch ( event.keyCode ) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv ); + if ( sel[ 0 ] ) { + $.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] ); + } + + onSelect = $.datepicker._get( inst, "onSelect" ); + if ( onSelect ) { + dateStr = $.datepicker._formatDate( inst ); + + // Trigger custom callback + onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + -$.datepicker._get( inst, "stepBigMonths" ) : + -$.datepicker._get( inst, "stepMonths" ) ), "M" ); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + +$.datepicker._get( inst, "stepBigMonths" ) : + +$.datepicker._get( inst, "stepMonths" ) ), "M" ); + break; // next month/year on page down/+ ctrl + case 35: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._clearDate( event.target ); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._gotoToday( event.target ); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" ); + } + handled = event.ctrlKey || event.metaKey; + + // -1 day on ctrl or command +left + if ( event.originalEvent.altKey ) { + $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + -$.datepicker._get( inst, "stepBigMonths" ) : + -$.datepicker._get( inst, "stepMonths" ) ), "M" ); + } + + // next month/year on alt +left on Mac + break; + case 38: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, -7, "D" ); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" ); + } + handled = event.ctrlKey || event.metaKey; + + // +1 day on ctrl or command +right + if ( event.originalEvent.altKey ) { + $.datepicker._adjustDate( event.target, ( event.ctrlKey ? + +$.datepicker._get( inst, "stepBigMonths" ) : + +$.datepicker._get( inst, "stepMonths" ) ), "M" ); + } + + // next month/year on alt +right + break; + case 40: if ( event.ctrlKey || event.metaKey ) { + $.datepicker._adjustDate( event.target, +7, "D" ); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home + $.datepicker._showDatepicker( this ); + } else { + handled = false; + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function( event ) { + var chars, chr, + inst = $.datepicker._getInst( event.target ); + + if ( $.datepicker._get( inst, "constrainInput" ) ) { + chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) ); + chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode ); + return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 ); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function( event ) { + var date, + inst = $.datepicker._getInst( event.target ); + + if ( inst.input.val() !== inst.lastVal ) { + try { + date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), + ( inst.input ? inst.input.val() : null ), + $.datepicker._getFormatConfig( inst ) ); + + if ( date ) { // only if valid + $.datepicker._setDateFromField( inst ); + $.datepicker._updateAlternate( inst ); + $.datepicker._updateDatepicker( inst ); + } + } catch ( err ) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. * If false returned from beforeShow event handler do not show. * @param input element - the input field attached to the date picker or * event - if triggered by focus */ - _showDatepicker: function( input ) { - input = input.target || input; - if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger - input = $( "input", input.parentNode )[ 0 ]; - } - - if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here - return; - } - - var inst, beforeShow, beforeShowSettings, isFixed, - offset, showAnim, duration; - - inst = $.datepicker._getInst( input ); - if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) { - $.datepicker._curInst.dpDiv.stop( true, true ); - if ( inst && $.datepicker._datepickerShowing ) { - $.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] ); - } - } - - beforeShow = $.datepicker._get( inst, "beforeShow" ); - beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {}; - if ( beforeShowSettings === false ) { - return; - } - datepicker_extendRemove( inst.settings, beforeShowSettings ); - - inst.lastVal = null; - $.datepicker._lastInput = input; - $.datepicker._setDateFromField( inst ); - - if ( $.datepicker._inDialog ) { // hide cursor - input.value = ""; - } - if ( !$.datepicker._pos ) { // position below input - $.datepicker._pos = $.datepicker._findPos( input ); - $.datepicker._pos[ 1 ] += input.offsetHeight; // add the height - } - - isFixed = false; - $( input ).parents().each( function() { - isFixed |= $( this ).css( "position" ) === "fixed"; - return !isFixed; - } ); - - offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] }; - $.datepicker._pos = null; - - //to avoid flashes on Firefox - inst.dpDiv.empty(); - - // determine sizing offscreen - inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } ); - $.datepicker._updateDatepicker( inst ); - - // fix width for dynamic number of date pickers - // and adjust position before showing - offset = $.datepicker._checkOffset( inst, offset, isFixed ); - inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ? - "static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none", - left: offset.left + "px", top: offset.top + "px" } ); - - if ( !inst.inline ) { - showAnim = $.datepicker._get( inst, "showAnim" ); - duration = $.datepicker._get( inst, "duration" ); - inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); - $.datepicker._datepickerShowing = true; - - if ( $.effects && $.effects.effect[ showAnim ] ) { - inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration ); - } else { - inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null ); - } - - if ( $.datepicker._shouldFocusInput( inst ) ) { - inst.input.trigger( "focus" ); - } - - $.datepicker._curInst = inst; - } - }, - - /* Generate the date picker content. */ - _updateDatepicker: function( inst ) { - this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) - datepicker_instActive = inst; // for delegate hover events - inst.dpDiv.empty().append( this._generateHTML( inst ) ); - this._attachHandlers( inst ); - - var origyearshtml, - numMonths = this._getNumberOfMonths( inst ), - cols = numMonths[ 1 ], - width = 17, - activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); - - if ( activeCell.length > 0 ) { - datepicker_handleMouseover.apply( activeCell.get( 0 ) ); - } - - inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" ); - if ( cols > 1 ) { - inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" ); - } - inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) + - "Class" ]( "ui-datepicker-multi" ); - inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) + - "Class" ]( "ui-datepicker-rtl" ); - - if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { - inst.input.trigger( "focus" ); - } - - // Deffered render of the years select (to avoid flashes on Firefox) - if ( inst.yearshtml ) { - origyearshtml = inst.yearshtml; - setTimeout( function() { - - //assure that inst.yearshtml didn't change. - if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { - inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml ); - } - origyearshtml = inst.yearshtml = null; - }, 0 ); - } - }, - - // #6694 - don't focus the input if it's already focused - // this breaks the change event in IE - // Support: IE and jQuery <1.9 - _shouldFocusInput: function( inst ) { - return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); - }, - - /* Check positioning to remain on screen. */ - _checkOffset: function( inst, offset, isFixed ) { - var dpWidth = inst.dpDiv.outerWidth(), - dpHeight = inst.dpDiv.outerHeight(), - inputWidth = inst.input ? inst.input.outerWidth() : 0, - inputHeight = inst.input ? inst.input.outerHeight() : 0, - viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ), - viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() ); - - offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 ); - offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0; - offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0; - - // Now check if datepicker is showing outside window viewport - move to a better place if so. - offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ? - Math.abs( offset.left + dpWidth - viewWidth ) : 0 ); - offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ? - Math.abs( dpHeight + inputHeight ) : 0 ); - - return offset; - }, - - /* Find an object's position on the screen. */ - _findPos: function( obj ) { - var position, - inst = this._getInst( obj ), - isRTL = this._get( inst, "isRTL" ); - - while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) { - obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; - } - - position = $( obj ).offset(); - return [ position.left, position.top ]; - }, - - /* Hide the date picker from view. + _showDatepicker: function( input ) { + input = input.target || input; + if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger + input = $( "input", input.parentNode )[ 0 ]; + } + + if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst( input ); + if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) { + $.datepicker._curInst.dpDiv.stop( true, true ); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] ); + } + } + + beforeShow = $.datepicker._get( inst, "beforeShow" ); + beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {}; + if ( beforeShowSettings === false ) { + return; + } + datepicker_extendRemove( inst.settings, beforeShowSettings ); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField( inst ); + + if ( $.datepicker._inDialog ) { // hide cursor + input.value = ""; + } + if ( !$.datepicker._pos ) { // position below input + $.datepicker._pos = $.datepicker._findPos( input ); + $.datepicker._pos[ 1 ] += input.offsetHeight; // add the height + } + + isFixed = false; + $( input ).parents().each( function() { + isFixed |= $( this ).css( "position" ) === "fixed"; + return !isFixed; + } ); + + offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] }; + $.datepicker._pos = null; + + //to avoid flashes on Firefox + inst.dpDiv.empty(); + + // determine sizing offscreen + inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } ); + $.datepicker._updateDatepicker( inst ); + + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset( inst, offset, isFixed ); + inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ? + "static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none", + left: offset.left + "px", top: offset.top + "px" } ); + + if ( !inst.inline ) { + showAnim = $.datepicker._get( inst, "showAnim" ); + duration = $.datepicker._get( inst, "duration" ); + inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration ); + } else { + inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null ); + } + + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.trigger( "focus" ); + } + + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function( inst ) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + datepicker_instActive = inst; // for delegate hover events + inst.dpDiv.empty().append( this._generateHTML( inst ) ); + this._attachHandlers( inst ); + + var origyearshtml, + numMonths = this._getNumberOfMonths( inst ), + cols = numMonths[ 1 ], + width = 17, + activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ), + onUpdateDatepicker = $.datepicker._get( inst, "onUpdateDatepicker" ); + + if ( activeCell.length > 0 ) { + datepicker_handleMouseover.apply( activeCell.get( 0 ) ); + } + + inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" ); + if ( cols > 1 ) { + inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" ); + } + inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) + + "Class" ]( "ui-datepicker-multi" ); + inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) + + "Class" ]( "ui-datepicker-rtl" ); + + if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.trigger( "focus" ); + } + + // Deffered render of the years select (to avoid flashes on Firefox) + if ( inst.yearshtml ) { + origyearshtml = inst.yearshtml; + setTimeout( function() { + + //assure that inst.yearshtml didn't change. + if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { + inst.dpDiv.find( "select.ui-datepicker-year" ).first().replaceWith( inst.yearshtml ); + } + origyearshtml = inst.yearshtml = null; + }, 0 ); + } + + if ( onUpdateDatepicker ) { + onUpdateDatepicker.apply( ( inst.input ? inst.input[ 0 ] : null ), [ inst ] ); + } + }, + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function( inst, offset, isFixed ) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ), + viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() ); + + offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 ); + offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0; + offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0; + + // Now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ? + Math.abs( offset.left + dpWidth - viewWidth ) : 0 ); + offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ? + Math.abs( dpHeight + inputHeight ) : 0 ); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function( obj ) { + var position, + inst = this._getInst( obj ), + isRTL = this._get( inst, "isRTL" ); + + while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.pseudos.hidden( obj ) ) ) { + obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; + } + + position = $( obj ).offset(); + return [ position.left, position.top ]; + }, + + /* Hide the date picker from view. * @param input element - the input field attached to the date picker */ - _hideDatepicker: function( input ) { - var showAnim, duration, postProcess, onClose, - inst = this._curInst; - - if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) { - return; - } - - if ( this._datepickerShowing ) { - showAnim = this._get( inst, "showAnim" ); - duration = this._get( inst, "duration" ); - postProcess = function() { - $.datepicker._tidyDialog( inst ); - }; - - // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed - if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { - inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess ); - } else { - inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" : - ( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess ); - } - - if ( !showAnim ) { - postProcess(); - } - this._datepickerShowing = false; - - onClose = this._get( inst, "onClose" ); - if ( onClose ) { - onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] ); - } - - this._lastInput = null; - if ( this._inDialog ) { - this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } ); - if ( $.blockUI ) { - $.unblockUI(); - $( "body" ).append( this.dpDiv ); - } - } - this._inDialog = false; - } - }, - - /* Tidy up after a dialog display. */ - _tidyDialog: function( inst ) { - inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" ); - }, - - /* Close date picker if clicked elsewhere. */ - _checkExternalClick: function( event ) { - if ( !$.datepicker._curInst ) { - return; - } - - var $target = $( event.target ), - inst = $.datepicker._getInst( $target[ 0 ] ); - - if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId && - $target.parents( "#" + $.datepicker._mainDivId ).length === 0 && - !$target.hasClass( $.datepicker.markerClassName ) && - !$target.closest( "." + $.datepicker._triggerClass ).length && - $.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) || - ( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) { - $.datepicker._hideDatepicker(); - } - }, - - /* Adjust one of the date sub-fields. */ - _adjustDate: function( id, offset, period ) { - var target = $( id ), - inst = this._getInst( target[ 0 ] ); - - if ( this._isDisabledDatepicker( target[ 0 ] ) ) { - return; - } - this._adjustInstDate( inst, offset + - ( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning - period ); - this._updateDatepicker( inst ); - }, - - /* Action for current link. */ - _gotoToday: function( id ) { - var date, - target = $( id ), - inst = this._getInst( target[ 0 ] ); - - if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) { - inst.selectedDay = inst.currentDay; - inst.drawMonth = inst.selectedMonth = inst.currentMonth; - inst.drawYear = inst.selectedYear = inst.currentYear; - } else { - date = new Date(); - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); - } - this._notifyChange( inst ); - this._adjustDate( target ); - }, - - /* Action for selecting a new month/year. */ - _selectMonthYear: function( id, select, period ) { - var target = $( id ), - inst = this._getInst( target[ 0 ] ); - - inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] = - inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] = - parseInt( select.options[ select.selectedIndex ].value, 10 ); - - this._notifyChange( inst ); - this._adjustDate( target ); - }, - - /* Action for selecting a day. */ - _selectDay: function( id, month, year, td ) { - var inst, - target = $( id ); - - if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) { - return; - } - - inst = this._getInst( target[ 0 ] ); - inst.selectedDay = inst.currentDay = $( "a", td ).html(); - inst.selectedMonth = inst.currentMonth = month; - inst.selectedYear = inst.currentYear = year; - this._selectDate( id, this._formatDate( inst, - inst.currentDay, inst.currentMonth, inst.currentYear ) ); - }, - - /* Erase the input field and hide the date picker. */ - _clearDate: function( id ) { - var target = $( id ); - this._selectDate( target, "" ); - }, - - /* Update the input field with the selected date. */ - _selectDate: function( id, dateStr ) { - var onSelect, - target = $( id ), - inst = this._getInst( target[ 0 ] ); - - dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) ); - if ( inst.input ) { - inst.input.val( dateStr ); - } - this._updateAlternate( inst ); - - onSelect = this._get( inst, "onSelect" ); - if ( onSelect ) { - onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback - } else if ( inst.input ) { - inst.input.trigger( "change" ); // fire the change event - } - - if ( inst.inline ) { - this._updateDatepicker( inst ); - } else { - this._hideDatepicker(); - this._lastInput = inst.input[ 0 ]; - if ( typeof( inst.input[ 0 ] ) !== "object" ) { - inst.input.trigger( "focus" ); // restore focus - } - this._lastInput = null; - } - }, - - /* Update any alternate field to synchronise with the main field. */ - _updateAlternate: function( inst ) { - var altFormat, date, dateStr, - altField = this._get( inst, "altField" ); - - if ( altField ) { // update alternate field too - altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); - date = this._getDate( inst ); - dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); - $( altField ).val( dateStr ); - } - }, - - /* Set as beforeShowDay function to prevent selection of weekends. + _hideDatepicker: function( input ) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) { + return; + } + + if ( this._datepickerShowing ) { + showAnim = this._get( inst, "showAnim" ); + duration = this._get( inst, "duration" ); + postProcess = function() { + $.datepicker._tidyDialog( inst ); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess ); + } else { + inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" : + ( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess ); + } + + if ( !showAnim ) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get( inst, "onClose" ); + if ( onClose ) { + onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] ); + } + + this._lastInput = null; + if ( this._inDialog ) { + this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } ); + if ( $.blockUI ) { + $.unblockUI(); + $( "body" ).append( this.dpDiv ); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function( inst ) { + inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" ); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function( event ) { + if ( !$.datepicker._curInst ) { + return; + } + + var $target = $( event.target ), + inst = $.datepicker._getInst( $target[ 0 ] ); + + if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId && + $target.parents( "#" + $.datepicker._mainDivId ).length === 0 && + !$target.hasClass( $.datepicker.markerClassName ) && + !$target.closest( "." + $.datepicker._triggerClass ).length && + $.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) || + ( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function( id, offset, period ) { + var target = $( id ), + inst = this._getInst( target[ 0 ] ); + + if ( this._isDisabledDatepicker( target[ 0 ] ) ) { + return; + } + this._adjustInstDate( inst, offset, period ); + this._updateDatepicker( inst ); + }, + + /* Action for current link. */ + _gotoToday: function( id ) { + var date, + target = $( id ), + inst = this._getInst( target[ 0 ] ); + + if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange( inst ); + this._adjustDate( target ); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function( id, select, period ) { + var target = $( id ), + inst = this._getInst( target[ 0 ] ); + + inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] = + inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] = + parseInt( select.options[ select.selectedIndex ].value, 10 ); + + this._notifyChange( inst ); + this._adjustDate( target ); + }, + + /* Action for selecting a day. */ + _selectDay: function( id, month, year, td ) { + var inst, + target = $( id ); + + if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) { + return; + } + + inst = this._getInst( target[ 0 ] ); + inst.selectedDay = inst.currentDay = parseInt( $( "a", td ).attr( "data-date" ) ); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate( id, this._formatDate( inst, + inst.currentDay, inst.currentMonth, inst.currentYear ) ); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function( id ) { + var target = $( id ); + this._selectDate( target, "" ); + }, + + /* Update the input field with the selected date. */ + _selectDate: function( id, dateStr ) { + var onSelect, + target = $( id ), + inst = this._getInst( target[ 0 ] ); + + dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) ); + if ( inst.input ) { + inst.input.val( dateStr ); + } + this._updateAlternate( inst ); + + onSelect = this._get( inst, "onSelect" ); + if ( onSelect ) { + onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback + } else if ( inst.input ) { + inst.input.trigger( "change" ); // fire the change event + } + + if ( inst.inline ) { + this._updateDatepicker( inst ); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[ 0 ]; + if ( typeof( inst.input[ 0 ] ) !== "object" ) { + inst.input.trigger( "focus" ); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function( inst ) { + var altFormat, date, dateStr, + altField = this._get( inst, "altField" ); + + if ( altField ) { // update alternate field too + altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); + date = this._getDate( inst ); + dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); + $( document ).find( altField ).val( dateStr ); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. * @param date Date - the date to customise * @return [boolean, string] - is this date selectable?, what is its CSS class? */ - noWeekends: function( date ) { - var day = date.getDay(); - return [ ( day > 0 && day < 6 ), "" ]; - }, + noWeekends: function( date ) { + var day = date.getDay(); + return [ ( day > 0 && day < 6 ), "" ]; + }, - /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. * @param date Date - the date to get the week for * @return number - the number of the week within the year that contains this date */ - iso8601Week: function( date ) { - var time, - checkDate = new Date( date.getTime() ); + iso8601Week: function( date ) { + var time, + checkDate = new Date( date.getTime() ); - // Find Thursday of this week starting on Monday - checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) ); + // Find Thursday of this week starting on Monday + checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) ); - time = checkDate.getTime(); - checkDate.setMonth( 0 ); // Compare with Jan 1 - checkDate.setDate( 1 ); - return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1; - }, + time = checkDate.getTime(); + checkDate.setMonth( 0 ); // Compare with Jan 1 + checkDate.setDate( 1 ); + return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1; + }, - /* Parse a string value into a date object. + /* Parse a string value into a date object. * See formatDate below for the possible formats. * * @param format string - the expected format of the date @@ -10694,192 +10911,192 @@ $.extend( Datepicker.prototype, { * monthNames string[12] - names of the months (optional) * @return Date - the extracted date value or null if value is blank */ - parseDate: function( format, value, settings ) { - if ( format == null || value == null ) { - throw "Invalid arguments"; - } - - value = ( typeof value === "object" ? value.toString() : value + "" ); - if ( value === "" ) { - return null; - } - - var iFormat, dim, extra, - iValue = 0, - shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff, - shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : - new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ), - dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, - dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, - monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, - monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, - year = -1, - month = -1, - day = -1, - doy = -1, - literal = false, - date, - - // Check whether a format character is doubled - lookAhead = function( match ) { - var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); - if ( matches ) { - iFormat++; - } - return matches; - }, - - // Extract a number from the string value - getNumber = function( match ) { - var isDoubled = lookAhead( match ), - size = ( match === "@" ? 14 : ( match === "!" ? 20 : - ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), - minSize = ( match === "y" ? size : 1 ), - digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), - num = value.substring( iValue ).match( digits ); - if ( !num ) { - throw "Missing number at position " + iValue; - } - iValue += num[ 0 ].length; - return parseInt( num[ 0 ], 10 ); - }, - - // Extract a name from the string value and convert to an index - getName = function( match, shortNames, longNames ) { - var index = -1, - names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) { - return [ [ k, v ] ]; - } ).sort( function( a, b ) { - return -( a[ 1 ].length - b[ 1 ].length ); - } ); - - $.each( names, function( i, pair ) { - var name = pair[ 1 ]; - if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) { - index = pair[ 0 ]; - iValue += name.length; - return false; - } - } ); - if ( index !== -1 ) { - return index + 1; - } else { - throw "Unknown name at position " + iValue; - } - }, - - // Confirm that a literal character matches the string value - checkLiteral = function() { - if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) { - throw "Unexpected literal at position " + iValue; - } - iValue++; - }; - - for ( iFormat = 0; iFormat < format.length; iFormat++ ) { - if ( literal ) { - if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { - literal = false; - } else { - checkLiteral(); - } - } else { - switch ( format.charAt( iFormat ) ) { - case "d": - day = getNumber( "d" ); - break; - case "D": - getName( "D", dayNamesShort, dayNames ); - break; - case "o": - doy = getNumber( "o" ); - break; - case "m": - month = getNumber( "m" ); - break; - case "M": - month = getName( "M", monthNamesShort, monthNames ); - break; - case "y": - year = getNumber( "y" ); - break; - case "@": - date = new Date( getNumber( "@" ) ); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); - break; - case "!": - date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); - break; - case "'": - if ( lookAhead( "'" ) ) { - checkLiteral(); - } else { - literal = true; - } - break; - default: - checkLiteral(); - } - } - } - - if ( iValue < value.length ) { - extra = value.substr( iValue ); - if ( !/^\s+/.test( extra ) ) { - throw "Extra/unparsed characters found in date: " + extra; - } - } - - if ( year === -1 ) { - year = new Date().getFullYear(); - } else if ( year < 100 ) { - year += new Date().getFullYear() - new Date().getFullYear() % 100 + - ( year <= shortYearCutoff ? 0 : -100 ); - } - - if ( doy > -1 ) { - month = 1; - day = doy; - do { - dim = this._getDaysInMonth( year, month - 1 ); - if ( day <= dim ) { - break; - } - month++; - day -= dim; - } while ( true ); - } - - date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); - if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { - throw "Invalid date"; // E.g. 31/02/00 - } - return date; - }, - - /* Standard date formats. */ - ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) - COOKIE: "D, dd M yy", - ISO_8601: "yy-mm-dd", - RFC_822: "D, d M y", - RFC_850: "DD, dd-M-y", - RFC_1036: "D, d M y", - RFC_1123: "D, d M yy", - RFC_2822: "D, d M yy", - RSS: "D, d M y", // RFC 822 - TICKS: "!", - TIMESTAMP: "@", - W3C: "yy-mm-dd", // ISO 8601 - - _ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) + - Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ), - - /* Format a date object into a string value. + parseDate: function( format, value, settings ) { + if ( format == null || value == null ) { + throw "Invalid arguments"; + } + + value = ( typeof value === "object" ? value.toString() : value + "" ); + if ( value === "" ) { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff, + shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ), + dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, + dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, + monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, + monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }, + + // Extract a number from the string value + getNumber = function( match ) { + var isDoubled = lookAhead( match ), + size = ( match === "@" ? 14 : ( match === "!" ? 20 : + ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), + minSize = ( match === "y" ? size : 1 ), + digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), + num = value.substring( iValue ).match( digits ); + if ( !num ) { + throw "Missing number at position " + iValue; + } + iValue += num[ 0 ].length; + return parseInt( num[ 0 ], 10 ); + }, + + // Extract a name from the string value and convert to an index + getName = function( match, shortNames, longNames ) { + var index = -1, + names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) { + return [ [ k, v ] ]; + } ).sort( function( a, b ) { + return -( a[ 1 ].length - b[ 1 ].length ); + } ); + + $.each( names, function( i, pair ) { + var name = pair[ 1 ]; + if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) { + index = pair[ 0 ]; + iValue += name.length; + return false; + } + } ); + if ( index !== -1 ) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + + // Confirm that a literal character matches the string value + checkLiteral = function() { + if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": + day = getNumber( "d" ); + break; + case "D": + getName( "D", dayNamesShort, dayNames ); + break; + case "o": + doy = getNumber( "o" ); + break; + case "m": + month = getNumber( "m" ); + break; + case "M": + month = getName( "M", monthNamesShort, monthNames ); + break; + case "y": + year = getNumber( "y" ); + break; + case "@": + date = new Date( getNumber( "@" ) ); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if ( lookAhead( "'" ) ) { + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if ( iValue < value.length ) { + extra = value.substr( iValue ); + if ( !/^\s+/.test( extra ) ) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if ( year === -1 ) { + year = new Date().getFullYear(); + } else if ( year < 100 ) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + ( year <= shortYearCutoff ? 0 : -100 ); + } + + if ( doy > -1 ) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth( year, month - 1 ); + if ( day <= dim ) { + break; + } + month++; + day -= dim; + } while ( true ); + } + + date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); + if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) + + Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ), + + /* Format a date object into a string value. * The format can be combinations of the following: * d - day of month (no leading zero) * dd - day of month (two digit) @@ -10907,780 +11124,857 @@ $.extend( Datepicker.prototype, { * monthNames string[12] - names of the months (optional) * @return string - the date in the above format */ - formatDate: function( format, date, settings ) { - if ( !date ) { - return ""; - } - - var iFormat, - dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, - dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, - monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, - monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, - - // Check whether a format character is doubled - lookAhead = function( match ) { - var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); - if ( matches ) { - iFormat++; - } - return matches; - }, - - // Format a number, with leading zero if necessary - formatNumber = function( match, value, len ) { - var num = "" + value; - if ( lookAhead( match ) ) { - while ( num.length < len ) { - num = "0" + num; - } - } - return num; - }, - - // Format a name, short or long as requested - formatName = function( match, value, shortNames, longNames ) { - return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] ); - }, - output = "", - literal = false; - - if ( date ) { - for ( iFormat = 0; iFormat < format.length; iFormat++ ) { - if ( literal ) { - if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { - literal = false; - } else { - output += format.charAt( iFormat ); - } - } else { - switch ( format.charAt( iFormat ) ) { - case "d": - output += formatNumber( "d", date.getDate(), 2 ); - break; - case "D": - output += formatName( "D", date.getDay(), dayNamesShort, dayNames ); - break; - case "o": - output += formatNumber( "o", - Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); - break; - case "m": - output += formatNumber( "m", date.getMonth() + 1, 2 ); - break; - case "M": - output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); - break; - case "y": - output += ( lookAhead( "y" ) ? date.getFullYear() : - ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); - break; - case "@": - output += date.getTime(); - break; - case "!": - output += date.getTime() * 10000 + this._ticksTo1970; - break; - case "'": - if ( lookAhead( "'" ) ) { - output += "'"; - } else { - literal = true; - } - break; - default: - output += format.charAt( iFormat ); - } - } - } - } - return output; - }, - - /* Extract all possible characters from the date format. */ - _possibleChars: function( format ) { - var iFormat, - chars = "", - literal = false, - - // Check whether a format character is doubled - lookAhead = function( match ) { - var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); - if ( matches ) { - iFormat++; - } - return matches; - }; - - for ( iFormat = 0; iFormat < format.length; iFormat++ ) { - if ( literal ) { - if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { - literal = false; - } else { - chars += format.charAt( iFormat ); - } - } else { - switch ( format.charAt( iFormat ) ) { - case "d": case "m": case "y": case "@": - chars += "0123456789"; - break; - case "D": case "M": - return null; // Accept anything - case "'": - if ( lookAhead( "'" ) ) { - chars += "'"; - } else { - literal = true; - } - break; - default: - chars += format.charAt( iFormat ); - } - } - } - return chars; - }, - - /* Get a setting value, defaulting if necessary. */ - _get: function( inst, name ) { - return inst.settings[ name ] !== undefined ? - inst.settings[ name ] : this._defaults[ name ]; - }, - - /* Parse existing date and initialise date picker. */ - _setDateFromField: function( inst, noDefault ) { - if ( inst.input.val() === inst.lastVal ) { - return; - } - - var dateFormat = this._get( inst, "dateFormat" ), - dates = inst.lastVal = inst.input ? inst.input.val() : null, - defaultDate = this._getDefaultDate( inst ), - date = defaultDate, - settings = this._getFormatConfig( inst ); - - try { - date = this.parseDate( dateFormat, dates, settings ) || defaultDate; - } catch ( event ) { - dates = ( noDefault ? "" : dates ); - } - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); - inst.currentDay = ( dates ? date.getDate() : 0 ); - inst.currentMonth = ( dates ? date.getMonth() : 0 ); - inst.currentYear = ( dates ? date.getFullYear() : 0 ); - this._adjustInstDate( inst ); - }, - - /* Retrieve the default date shown on opening. */ - _getDefaultDate: function( inst ) { - return this._restrictMinMax( inst, - this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) ); - }, - - /* A date may be specified as an exact value or a relative one. */ - _determineDate: function( inst, date, defaultDate ) { - var offsetNumeric = function( offset ) { - var date = new Date(); - date.setDate( date.getDate() + offset ); - return date; - }, - offsetString = function( offset ) { - try { - return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), - offset, $.datepicker._getFormatConfig( inst ) ); - } - catch ( e ) { - - // Ignore - } - - var date = ( offset.toLowerCase().match( /^c/ ) ? - $.datepicker._getDate( inst ) : null ) || new Date(), - year = date.getFullYear(), - month = date.getMonth(), - day = date.getDate(), - pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, - matches = pattern.exec( offset ); - - while ( matches ) { - switch ( matches[ 2 ] || "d" ) { - case "d" : case "D" : - day += parseInt( matches[ 1 ], 10 ); break; - case "w" : case "W" : - day += parseInt( matches[ 1 ], 10 ) * 7; break; - case "m" : case "M" : - month += parseInt( matches[ 1 ], 10 ); - day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); - break; - case "y": case "Y" : - year += parseInt( matches[ 1 ], 10 ); - day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); - break; - } - matches = pattern.exec( offset ); - } - return new Date( year, month, day ); - }, - newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : - ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); - - newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate ); - if ( newDate ) { - newDate.setHours( 0 ); - newDate.setMinutes( 0 ); - newDate.setSeconds( 0 ); - newDate.setMilliseconds( 0 ); - } - return this._daylightSavingAdjust( newDate ); - }, - - /* Handle switch to/from daylight saving. + formatDate: function( format, date, settings ) { + if ( !date ) { + return ""; + } + + var iFormat, + dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, + dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, + monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, + monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }, + + // Format a number, with leading zero if necessary + formatNumber = function( match, value, len ) { + var num = "" + value; + if ( lookAhead( match ) ) { + while ( num.length < len ) { + num = "0" + num; + } + } + return num; + }, + + // Format a name, short or long as requested + formatName = function( match, value, shortNames, longNames ) { + return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] ); + }, + output = "", + literal = false; + + if ( date ) { + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + output += format.charAt( iFormat ); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": + output += formatNumber( "d", date.getDate(), 2 ); + break; + case "D": + output += formatName( "D", date.getDay(), dayNamesShort, dayNames ); + break; + case "o": + output += formatNumber( "o", + Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); + break; + case "m": + output += formatNumber( "m", date.getMonth() + 1, 2 ); + break; + case "M": + output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); + break; + case "y": + output += ( lookAhead( "y" ) ? date.getFullYear() : + ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if ( lookAhead( "'" ) ) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt( iFormat ); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function( format ) { + var iFormat, + chars = "", + literal = false, + + // Check whether a format character is doubled + lookAhead = function( match ) { + var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); + if ( matches ) { + iFormat++; + } + return matches; + }; + + for ( iFormat = 0; iFormat < format.length; iFormat++ ) { + if ( literal ) { + if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { + literal = false; + } else { + chars += format.charAt( iFormat ); + } + } else { + switch ( format.charAt( iFormat ) ) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if ( lookAhead( "'" ) ) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt( iFormat ); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function( inst, name ) { + return inst.settings[ name ] !== undefined ? + inst.settings[ name ] : this._defaults[ name ]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function( inst, noDefault ) { + if ( inst.input.val() === inst.lastVal ) { + return; + } + + var dateFormat = this._get( inst, "dateFormat" ), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate( inst ), + date = defaultDate, + settings = this._getFormatConfig( inst ); + + try { + date = this.parseDate( dateFormat, dates, settings ) || defaultDate; + } catch ( event ) { + dates = ( noDefault ? "" : dates ); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = ( dates ? date.getDate() : 0 ); + inst.currentMonth = ( dates ? date.getMonth() : 0 ); + inst.currentYear = ( dates ? date.getFullYear() : 0 ); + this._adjustInstDate( inst ); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function( inst ) { + return this._restrictMinMax( inst, + this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) ); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function( inst, date, defaultDate ) { + var offsetNumeric = function( offset ) { + var date = new Date(); + date.setDate( date.getDate() + offset ); + return date; + }, + offsetString = function( offset ) { + try { + return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), + offset, $.datepicker._getFormatConfig( inst ) ); + } catch ( e ) { + + // Ignore + } + + var date = ( offset.toLowerCase().match( /^c/ ) ? + $.datepicker._getDate( inst ) : null ) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec( offset ); + + while ( matches ) { + switch ( matches[ 2 ] || "d" ) { + case "d" : case "D" : + day += parseInt( matches[ 1 ], 10 ); break; + case "w" : case "W" : + day += parseInt( matches[ 1 ], 10 ) * 7; break; + case "m" : case "M" : + month += parseInt( matches[ 1 ], 10 ); + day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); + break; + case "y": case "Y" : + year += parseInt( matches[ 1 ], 10 ); + day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); + break; + } + matches = pattern.exec( offset ); + } + return new Date( year, month, day ); + }, + newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : + ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); + + newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate ); + if ( newDate ) { + newDate.setHours( 0 ); + newDate.setMinutes( 0 ); + newDate.setSeconds( 0 ); + newDate.setMilliseconds( 0 ); + } + return this._daylightSavingAdjust( newDate ); + }, + + /* Handle switch to/from daylight saving. * Hours may be non-zero on daylight saving cut-over: * > 12 when midnight changeover, but then cannot generate * midnight datetime, so jump to 1AM, otherwise reset. * @param date (Date) the date to check * @return (Date) the corrected date */ - _daylightSavingAdjust: function( date ) { - if ( !date ) { - return null; - } - date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 ); - return date; - }, - - /* Set the date(s) directly. */ - _setDate: function( inst, date, noChange ) { - var clear = !date, - origMonth = inst.selectedMonth, - origYear = inst.selectedYear, - newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) ); - - inst.selectedDay = inst.currentDay = newDate.getDate(); - inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); - inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); - if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) { - this._notifyChange( inst ); - } - this._adjustInstDate( inst ); - if ( inst.input ) { - inst.input.val( clear ? "" : this._formatDate( inst ) ); - } - }, - - /* Retrieve the date(s) directly. */ - _getDate: function( inst ) { - var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : - this._daylightSavingAdjust( new Date( - inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); - return startDate; - }, - - /* Attach the onxxx handlers. These are declared statically so + _daylightSavingAdjust: function( date ) { + if ( !date ) { + return null; + } + date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 ); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function( inst, date, noChange ) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) ); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) { + this._notifyChange( inst ); + } + this._adjustInstDate( inst ); + if ( inst.input ) { + inst.input.val( clear ? "" : this._formatDate( inst ) ); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function( inst ) { + var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : + this._daylightSavingAdjust( new Date( + inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so * they work with static code transformers like Caja. */ - _attachHandlers: function( inst ) { - var stepMonths = this._get( inst, "stepMonths" ), - id = "#" + inst.id.replace( /\\\\/g, "\\" ); - inst.dpDiv.find( "[data-handler]" ).map( function() { - var handler = { - prev: function() { - $.datepicker._adjustDate( id, -stepMonths, "M" ); - }, - next: function() { - $.datepicker._adjustDate( id, +stepMonths, "M" ); - }, - hide: function() { - $.datepicker._hideDatepicker(); - }, - today: function() { - $.datepicker._gotoToday( id ); - }, - selectDay: function() { - $.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this ); - return false; - }, - selectMonth: function() { - $.datepicker._selectMonthYear( id, this, "M" ); - return false; - }, - selectYear: function() { - $.datepicker._selectMonthYear( id, this, "Y" ); - return false; - } - }; - $( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] ); - } ); - }, - - /* Generate the HTML for the current state of the date picker. */ - _generateHTML: function( inst ) { - var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, - controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, - monthNames, monthNamesShort, beforeShowDay, showOtherMonths, - selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, - cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, - printDate, dRow, tbody, daySettings, otherMonth, unselectable, - tempDate = new Date(), - today = this._daylightSavingAdjust( - new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time - isRTL = this._get( inst, "isRTL" ), - showButtonPanel = this._get( inst, "showButtonPanel" ), - hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), - navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), - numMonths = this._getNumberOfMonths( inst ), - showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), - stepMonths = this._get( inst, "stepMonths" ), - isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), - currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : - new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), - minDate = this._getMinMaxDate( inst, "min" ), - maxDate = this._getMinMaxDate( inst, "max" ), - drawMonth = inst.drawMonth - showCurrentAtPos, - drawYear = inst.drawYear; - - if ( drawMonth < 0 ) { - drawMonth += 12; - drawYear--; - } - if ( maxDate ) { - maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), - maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); - maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); - while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { - drawMonth--; - if ( drawMonth < 0 ) { - drawMonth = 11; - drawYear--; - } - } - } - inst.drawMonth = drawMonth; - inst.drawYear = drawYear; - - prevText = this._get( inst, "prevText" ); - prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, - this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), - this._getFormatConfig( inst ) ) ); - - prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ? - "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" + - " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" : - ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) ); - - nextText = this._get( inst, "nextText" ); - nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, - this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), - this._getFormatConfig( inst ) ) ); - - next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ? - "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" + - " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" : - ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) ); - - currentText = this._get( inst, "currentText" ); - gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); - currentText = ( !navigationAsDateFormat ? currentText : - this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); - - controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" + - this._get( inst, "closeText" ) + "</button>" : "" ); - - buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) + - ( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" + - ">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : ""; - - firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); - firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); - - showWeek = this._get( inst, "showWeek" ); - dayNames = this._get( inst, "dayNames" ); - dayNamesMin = this._get( inst, "dayNamesMin" ); - monthNames = this._get( inst, "monthNames" ); - monthNamesShort = this._get( inst, "monthNamesShort" ); - beforeShowDay = this._get( inst, "beforeShowDay" ); - showOtherMonths = this._get( inst, "showOtherMonths" ); - selectOtherMonths = this._get( inst, "selectOtherMonths" ); - defaultDate = this._getDefaultDate( inst ); - html = ""; - - for ( row = 0; row < numMonths[ 0 ]; row++ ) { - group = ""; - this.maxRows = 4; - for ( col = 0; col < numMonths[ 1 ]; col++ ) { - selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); - cornerClass = " ui-corner-all"; - calender = ""; - if ( isMultiMonth ) { - calender += "<div class='ui-datepicker-group"; - if ( numMonths[ 1 ] > 1 ) { - switch ( col ) { - case 0: calender += " ui-datepicker-group-first"; - cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break; - case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last"; - cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break; - default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; - } - } - calender += "'>"; - } - calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + - ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + - ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + - this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, - row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers - "</div><table class='ui-datepicker-calendar'><thead>" + - "<tr>"; - thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" ); - for ( dow = 0; dow < 7; dow++ ) { // days of the week - day = ( dow + firstDay ) % 7; - thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + - "<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>"; - } - calender += thead + "</tr></thead><tbody>"; - daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); - if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { - inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); - } - leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; - curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate - numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) - this.maxRows = numRows; - printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); - for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows - calender += "<tr>"; - tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" + - this._get( inst, "calculateWeek" )( printDate ) + "</td>" ); - for ( dow = 0; dow < 7; dow++ ) { // create date picker days - daySettings = ( beforeShowDay ? - beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); - otherMonth = ( printDate.getMonth() !== drawMonth ); - unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || - ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); - tbody += "<td class='" + - ( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends - ( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months - ( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key - ( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ? - - // or defaultDate is current printedDate and defaultDate is selectedDate - " " + this._dayOverClass : "" ) + // highlight selected day - ( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) + // highlight unselectable days - ( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates - ( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day - ( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different) - ( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "'" ) + "'" : "" ) + // cell title - ( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions - ( otherMonth && !showOtherMonths ? " " : // display for other months - ( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + - ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + - ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day - ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months - "' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date - printDate.setDate( printDate.getDate() + 1 ); - printDate = this._daylightSavingAdjust( printDate ); - } - calender += tbody + "</tr>"; - } - drawMonth++; - if ( drawMonth > 11 ) { - drawMonth = 0; - drawYear++; - } - calender += "</tbody></table>" + ( isMultiMonth ? "</div>" + - ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" ); - group += calender; - } - html += group; - } - html += buttonPanel; - inst._keyEvent = false; - return html; - }, - - /* Generate the month and year header. */ - _generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate, - secondary, monthNames, monthNamesShort ) { - - var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, - changeMonth = this._get( inst, "changeMonth" ), - changeYear = this._get( inst, "changeYear" ), - showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), - html = "<div class='ui-datepicker-title'>", - monthHtml = ""; - - // Month selection - if ( secondary || !changeMonth ) { - monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>"; - } else { - inMinYear = ( minDate && minDate.getFullYear() === drawYear ); - inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); - monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>"; - for ( month = 0; month < 12; month++ ) { - if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) { - monthHtml += "<option value='" + month + "'" + - ( month === drawMonth ? " selected='selected'" : "" ) + - ">" + monthNamesShort[ month ] + "</option>"; - } - } - monthHtml += "</select>"; - } - - if ( !showMonthAfterYear ) { - html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? " " : "" ); - } - - // Year selection - if ( !inst.yearshtml ) { - inst.yearshtml = ""; - if ( secondary || !changeYear ) { - html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; - } else { - - // determine range of years to display - years = this._get( inst, "yearRange" ).split( ":" ); - thisYear = new Date().getFullYear(); - determineYear = function( value ) { - var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) : - ( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) : - parseInt( value, 10 ) ) ); - return ( isNaN( year ) ? thisYear : year ); - }; - year = determineYear( years[ 0 ] ); - endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); - year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); - endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); - inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"; - for ( ; year <= endYear; year++ ) { - inst.yearshtml += "<option value='" + year + "'" + - ( year === drawYear ? " selected='selected'" : "" ) + - ">" + year + "</option>"; - } - inst.yearshtml += "</select>"; - - html += inst.yearshtml; - inst.yearshtml = null; - } - } - - html += this._get( inst, "yearSuffix" ); - if ( showMonthAfterYear ) { - html += ( secondary || !( changeMonth && changeYear ) ? " " : "" ) + monthHtml; - } - html += "</div>"; // Close datepicker_header - return html; - }, - - /* Adjust one of the date sub-fields. */ - _adjustInstDate: function( inst, offset, period ) { - var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), - month = inst.selectedMonth + ( period === "M" ? offset : 0 ), - day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), - date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); - - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); - if ( period === "M" || period === "Y" ) { - this._notifyChange( inst ); - } - }, - - /* Ensure a date is within any min/max bounds. */ - _restrictMinMax: function( inst, date ) { - var minDate = this._getMinMaxDate( inst, "min" ), - maxDate = this._getMinMaxDate( inst, "max" ), - newDate = ( minDate && date < minDate ? minDate : date ); - return ( maxDate && newDate > maxDate ? maxDate : newDate ); - }, - - /* Notify change of month/year. */ - _notifyChange: function( inst ) { - var onChange = this._get( inst, "onChangeMonthYear" ); - if ( onChange ) { - onChange.apply( ( inst.input ? inst.input[ 0 ] : null ), - [ inst.selectedYear, inst.selectedMonth + 1, inst ] ); - } - }, - - /* Determine the number of months to show. */ - _getNumberOfMonths: function( inst ) { - var numMonths = this._get( inst, "numberOfMonths" ); - return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) ); - }, - - /* Determine the current maximum date - ensure no time components are set. */ - _getMinMaxDate: function( inst, minMax ) { - return this._determineDate( inst, this._get( inst, minMax + "Date" ), null ); - }, - - /* Find the number of days in a given month. */ - _getDaysInMonth: function( year, month ) { - return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); - }, - - /* Find the day of the week of the first of a month. */ - _getFirstDayOfMonth: function( year, month ) { - return new Date( year, month, 1 ).getDay(); - }, - - /* Determines if we should allow a "next/prev" month display change. */ - _canAdjustMonth: function( inst, offset, curYear, curMonth ) { - var numMonths = this._getNumberOfMonths( inst ), - date = this._daylightSavingAdjust( new Date( curYear, - curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); - - if ( offset < 0 ) { - date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) ); - } - return this._isInRange( inst, date ); - }, - - /* Is the given date in the accepted range? */ - _isInRange: function( inst, date ) { - var yearSplit, currentYear, - minDate = this._getMinMaxDate( inst, "min" ), - maxDate = this._getMinMaxDate( inst, "max" ), - minYear = null, - maxYear = null, - years = this._get( inst, "yearRange" ); - if ( years ) { - yearSplit = years.split( ":" ); - currentYear = new Date().getFullYear(); - minYear = parseInt( yearSplit[ 0 ], 10 ); - maxYear = parseInt( yearSplit[ 1 ], 10 ); - if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) { - minYear += currentYear; - } - if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) { - maxYear += currentYear; - } - } - - return ( ( !minDate || date.getTime() >= minDate.getTime() ) && - ( !maxDate || date.getTime() <= maxDate.getTime() ) && - ( !minYear || date.getFullYear() >= minYear ) && - ( !maxYear || date.getFullYear() <= maxYear ) ); - }, - - /* Provide the configuration settings for formatting/parsing. */ - _getFormatConfig: function( inst ) { - var shortYearCutoff = this._get( inst, "shortYearCutoff" ); - shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff : - new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) ); - return { shortYearCutoff: shortYearCutoff, - dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ), - monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) }; - }, - - /* Format the given date for display. */ - _formatDate: function( inst, day, month, year ) { - if ( !day ) { - inst.currentDay = inst.selectedDay; - inst.currentMonth = inst.selectedMonth; - inst.currentYear = inst.selectedYear; - } - var date = ( day ? ( typeof day === "object" ? day : - this._daylightSavingAdjust( new Date( year, month, day ) ) ) : - this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); - return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); - } -} ); - -/* + _attachHandlers: function( inst ) { + var stepMonths = this._get( inst, "stepMonths" ), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find( "[data-handler]" ).map( function() { + var handler = { + prev: function() { + $.datepicker._adjustDate( id, -stepMonths, "M" ); + }, + next: function() { + $.datepicker._adjustDate( id, +stepMonths, "M" ); + }, + hide: function() { + $.datepicker._hideDatepicker(); + }, + today: function() { + $.datepicker._gotoToday( id ); + }, + selectDay: function() { + $.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this ); + return false; + }, + selectMonth: function() { + $.datepicker._selectMonthYear( id, this, "M" ); + return false; + }, + selectYear: function() { + $.datepicker._selectMonthYear( id, this, "Y" ); + return false; + } + }; + $( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] ); + } ); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function( inst ) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time + isRTL = this._get( inst, "isRTL" ), + showButtonPanel = this._get( inst, "showButtonPanel" ), + hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), + navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), + numMonths = this._getNumberOfMonths( inst ), + showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), + stepMonths = this._get( inst, "stepMonths" ), + isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), + currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : + new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), + minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if ( drawMonth < 0 ) { + drawMonth += 12; + drawYear--; + } + if ( maxDate ) { + maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), + maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); + maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); + while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { + drawMonth--; + if ( drawMonth < 0 ) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get( inst, "prevText" ); + prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, + this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), + this._getFormatConfig( inst ) ) ); + + if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) { + prev = $( "<a>" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all", + "data-handler": "prev", + "data-event": "click", + title: prevText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "e" : "w" ) ) + .text( prevText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + prev = ""; + } else { + prev = $( "<a>" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all ui-state-disabled", + title: prevText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "e" : "w" ) ) + .text( prevText ) + )[ 0 ].outerHTML; + } + + nextText = this._get( inst, "nextText" ); + nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, + this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), + this._getFormatConfig( inst ) ) ); + + if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) { + next = $( "<a>" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all", + "data-handler": "next", + "data-event": "click", + title: nextText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "w" : "e" ) ) + .text( nextText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + next = ""; + } else { + next = $( "<a>" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all ui-state-disabled", + title: nextText + } ) + .append( + $( "<span>" ) + .attr( "class", "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "w" : "e" ) ) + .text( nextText ) + )[ 0 ].outerHTML; + } + + currentText = this._get( inst, "currentText" ); + gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); + currentText = ( !navigationAsDateFormat ? currentText : + this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); + + controls = ""; + if ( !inst.inline ) { + controls = $( "<button>" ) + .attr( { + type: "button", + "class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all", + "data-handler": "hide", + "data-event": "click" + } ) + .text( this._get( inst, "closeText" ) )[ 0 ].outerHTML; + } + + buttonPanel = ""; + if ( showButtonPanel ) { + buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" ) + .append( isRTL ? controls : "" ) + .append( this._isInRange( inst, gotoDate ) ? + $( "<button>" ) + .attr( { + type: "button", + "class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all", + "data-handler": "today", + "data-event": "click" + } ) + .text( currentText ) : + "" ) + .append( isRTL ? "" : controls )[ 0 ].outerHTML; + } + + firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); + firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); + + showWeek = this._get( inst, "showWeek" ); + dayNames = this._get( inst, "dayNames" ); + dayNamesMin = this._get( inst, "dayNamesMin" ); + monthNames = this._get( inst, "monthNames" ); + monthNamesShort = this._get( inst, "monthNamesShort" ); + beforeShowDay = this._get( inst, "beforeShowDay" ); + showOtherMonths = this._get( inst, "showOtherMonths" ); + selectOtherMonths = this._get( inst, "selectOtherMonths" ); + defaultDate = this._getDefaultDate( inst ); + html = ""; + + for ( row = 0; row < numMonths[ 0 ]; row++ ) { + group = ""; + this.maxRows = 4; + for ( col = 0; col < numMonths[ 1 ]; col++ ) { + selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); + cornerClass = " ui-corner-all"; + calender = ""; + if ( isMultiMonth ) { + calender += "<div class='ui-datepicker-group"; + if ( numMonths[ 1 ] > 1 ) { + switch ( col ) { + case 0: calender += " ui-datepicker-group-first"; + cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break; + case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last"; + cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break; + default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; + } + } + calender += "'>"; + } + calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + + ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + + ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + + this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers + "</div><table class='ui-datepicker-calendar'><thead>" + + "<tr>"; + thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" ); + for ( dow = 0; dow < 7; dow++ ) { // days of the week + day = ( dow + firstDay ) % 7; + thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + + "<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>"; + } + calender += thead + "</tr></thead><tbody>"; + daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); + if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { + inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); + } + leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; + curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate + numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); + for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows + calender += "<tr>"; + tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" + + this._get( inst, "calculateWeek" )( printDate ) + "</td>" ); + for ( dow = 0; dow < 7; dow++ ) { // create date picker days + daySettings = ( beforeShowDay ? + beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); + otherMonth = ( printDate.getMonth() !== drawMonth ); + unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || + ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); + tbody += "<td class='" + + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends + ( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months + ( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key + ( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ? + + // or defaultDate is current printedDate and defaultDate is selectedDate + " " + this._dayOverClass : "" ) + // highlight selected day + ( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) + // highlight unselectable days + ( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates + ( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day + ( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different) + ( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "'" ) + "'" : "" ) + // cell title + ( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions + ( otherMonth && !showOtherMonths ? " " : // display for other months + ( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + + ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + + ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day + ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months + "' href='#' aria-current='" + ( printDate.getTime() === currentDate.getTime() ? "true" : "false" ) + // mark date as selected for screen reader + "' data-date='" + printDate.getDate() + // store date as data + "'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date + printDate.setDate( printDate.getDate() + 1 ); + printDate = this._daylightSavingAdjust( printDate ); + } + calender += tbody + "</tr>"; + } + drawMonth++; + if ( drawMonth > 11 ) { + drawMonth = 0; + drawYear++; + } + calender += "</tbody></table>" + ( isMultiMonth ? "</div>" + + ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" ); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort ) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get( inst, "changeMonth" ), + changeYear = this._get( inst, "changeYear" ), + showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), + selectMonthLabel = this._get( inst, "selectMonthLabel" ), + selectYearLabel = this._get( inst, "selectYearLabel" ), + html = "<div class='ui-datepicker-title'>", + monthHtml = ""; + + // Month selection + if ( secondary || !changeMonth ) { + monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>"; + } else { + inMinYear = ( minDate && minDate.getFullYear() === drawYear ); + inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); + monthHtml += "<select class='ui-datepicker-month' aria-label='" + selectMonthLabel + "' data-handler='selectMonth' data-event='change'>"; + for ( month = 0; month < 12; month++ ) { + if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) { + monthHtml += "<option value='" + month + "'" + + ( month === drawMonth ? " selected='selected'" : "" ) + + ">" + monthNamesShort[ month ] + "</option>"; + } + } + monthHtml += "</select>"; + } + + if ( !showMonthAfterYear ) { + html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? " " : "" ); + } + + // Year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if ( secondary || !changeYear ) { + html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; + } else { + + // determine range of years to display + years = this._get( inst, "yearRange" ).split( ":" ); + thisYear = new Date().getFullYear(); + determineYear = function( value ) { + var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) : + ( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) : + parseInt( value, 10 ) ) ); + return ( isNaN( year ) ? thisYear : year ); + }; + year = determineYear( years[ 0 ] ); + endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); + year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); + endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); + inst.yearshtml += "<select class='ui-datepicker-year' aria-label='" + selectYearLabel + "' data-handler='selectYear' data-event='change'>"; + for ( ; year <= endYear; year++ ) { + inst.yearshtml += "<option value='" + year + "'" + + ( year === drawYear ? " selected='selected'" : "" ) + + ">" + year + "</option>"; + } + inst.yearshtml += "</select>"; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get( inst, "yearSuffix" ); + if ( showMonthAfterYear ) { + html += ( secondary || !( changeMonth && changeYear ) ? " " : "" ) + monthHtml; + } + html += "</div>"; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function( inst, offset, period ) { + var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), + month = inst.selectedMonth + ( period === "M" ? offset : 0 ), + day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), + date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if ( period === "M" || period === "Y" ) { + this._notifyChange( inst ); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function( inst, date ) { + var minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + newDate = ( minDate && date < minDate ? minDate : date ); + return ( maxDate && newDate > maxDate ? maxDate : newDate ); + }, + + /* Notify change of month/year. */ + _notifyChange: function( inst ) { + var onChange = this._get( inst, "onChangeMonthYear" ); + if ( onChange ) { + onChange.apply( ( inst.input ? inst.input[ 0 ] : null ), + [ inst.selectedYear, inst.selectedMonth + 1, inst ] ); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function( inst ) { + var numMonths = this._get( inst, "numberOfMonths" ); + return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) ); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function( inst, minMax ) { + return this._determineDate( inst, this._get( inst, minMax + "Date" ), null ); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function( year, month ) { + return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function( year, month ) { + return new Date( year, month, 1 ).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function( inst, offset, curYear, curMonth ) { + var numMonths = this._getNumberOfMonths( inst ), + date = this._daylightSavingAdjust( new Date( curYear, + curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); + + if ( offset < 0 ) { + date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) ); + } + return this._isInRange( inst, date ); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function( inst, date ) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate( inst, "min" ), + maxDate = this._getMinMaxDate( inst, "max" ), + minYear = null, + maxYear = null, + years = this._get( inst, "yearRange" ); + if ( years ) { + yearSplit = years.split( ":" ); + currentYear = new Date().getFullYear(); + minYear = parseInt( yearSplit[ 0 ], 10 ); + maxYear = parseInt( yearSplit[ 1 ], 10 ); + if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) { + minYear += currentYear; + } + if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) { + maxYear += currentYear; + } + } + + return ( ( !minDate || date.getTime() >= minDate.getTime() ) && + ( !maxDate || date.getTime() <= maxDate.getTime() ) && + ( !minYear || date.getFullYear() >= minYear ) && + ( !maxYear || date.getFullYear() <= maxYear ) ); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function( inst ) { + var shortYearCutoff = this._get( inst, "shortYearCutoff" ); + shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) ); + return { shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ), + monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) }; + }, + + /* Format the given date for display. */ + _formatDate: function( inst, day, month, year ) { + if ( !day ) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = ( day ? ( typeof day === "object" ? day : + this._daylightSavingAdjust( new Date( year, month, day ) ) ) : + this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); + return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); + } + } ); + + /* * Bind hover events for datepicker elements. * Done via delegate so the binding only occurs once in the lifetime of the parent div. * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. */ -function datepicker_bindHover( dpDiv ) { - var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; - return dpDiv.on( "mouseout", selector, function() { - $( this ).removeClass( "ui-state-hover" ); - if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { - $( this ).removeClass( "ui-datepicker-prev-hover" ); - } - if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { - $( this ).removeClass( "ui-datepicker-next-hover" ); - } - } ) - .on( "mouseover", selector, datepicker_handleMouseover ); -} - -function datepicker_handleMouseover() { - if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) { - $( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" ); - $( this ).addClass( "ui-state-hover" ); - if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { - $( this ).addClass( "ui-datepicker-prev-hover" ); - } - if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { - $( this ).addClass( "ui-datepicker-next-hover" ); - } - } -} - -/* jQuery extend now ignores nulls! */ -function datepicker_extendRemove( target, props ) { - $.extend( target, props ); - for ( var name in props ) { - if ( props[ name ] == null ) { - target[ name ] = props[ name ]; - } - } - return target; -} - -/* Invoke the datepicker functionality. + function datepicker_bindHover( dpDiv ) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.on( "mouseout", selector, function() { + $( this ).removeClass( "ui-state-hover" ); + if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { + $( this ).removeClass( "ui-datepicker-prev-hover" ); + } + if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { + $( this ).removeClass( "ui-datepicker-next-hover" ); + } + } ) + .on( "mouseover", selector, datepicker_handleMouseover ); + } + + function datepicker_handleMouseover() { + if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) { + $( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" ); + $( this ).addClass( "ui-state-hover" ); + if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { + $( this ).addClass( "ui-datepicker-prev-hover" ); + } + if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { + $( this ).addClass( "ui-datepicker-next-hover" ); + } + } + } + + /* jQuery extend now ignores nulls! */ + function datepicker_extendRemove( target, props ) { + $.extend( target, props ); + for ( var name in props ) { + if ( props[ name ] == null ) { + target[ name ] = props[ name ]; + } + } + return target; + } + + /* Invoke the datepicker functionality. @param options string - a command, optionally followed by additional parameters or Object - settings for attaching new datepicker functionality @return jQuery object */ -$.fn.datepicker = function( options ) { - - /* Verify an empty collection wasn't passed - Fixes #6976 */ - if ( !this.length ) { - return this; - } - - /* Initialise the date picker. */ - if ( !$.datepicker.initialized ) { - $( document ).on( "mousedown", $.datepicker._checkExternalClick ); - $.datepicker.initialized = true; - } - - /* Append datepicker main container to body if not exist. */ - if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) { - $( "body" ).append( $.datepicker.dpDiv ); - } - - var otherArgs = Array.prototype.slice.call( arguments, 1 ); - if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) { - return $.datepicker[ "_" + options + "Datepicker" ]. - apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); - } - if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) { - return $.datepicker[ "_" + options + "Datepicker" ]. - apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); - } - return this.each( function() { - typeof options === "string" ? - $.datepicker[ "_" + options + "Datepicker" ]. - apply( $.datepicker, [ this ].concat( otherArgs ) ) : - $.datepicker._attachDatepicker( this, options ); - } ); -}; - -$.datepicker = new Datepicker(); // singleton instance -$.datepicker.initialized = false; -$.datepicker.uuid = new Date().getTime(); -$.datepicker.version = "1.12.1"; - -var widgetsDatepicker = $.datepicker; - - -/*! - * jQuery UI Dialog 1.12.1 + $.fn.datepicker = function( options ) { + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if ( !$.datepicker.initialized ) { + $( document ).on( "mousedown", $.datepicker._checkExternalClick ); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) { + $( "body" ).append( $.datepicker.dpDiv ); + } + + var otherArgs = Array.prototype.slice.call( arguments, 1 ); + if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) { + return $.datepicker[ "_" + options + "Datepicker" ]. + apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); + } + if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) { + return $.datepicker[ "_" + options + "Datepicker" ]. + apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); + } + return this.each( function() { + if ( typeof options === "string" ) { + $.datepicker[ "_" + options + "Datepicker" ] + .apply( $.datepicker, [ this ].concat( otherArgs ) ); + } else { + $.datepicker._attachDatepicker( this, options ); + } + } ); + }; + + $.datepicker = new Datepicker(); // singleton instance + $.datepicker.initialized = false; + $.datepicker.uuid = new Date().getTime(); + $.datepicker.version = "1.13.0"; + + var widgetsDatepicker = $.datepicker; + + + /*! + * jQuery UI Dialog 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -11698,904 +11992,914 @@ var widgetsDatepicker = $.datepicker; //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.dialog", { - version: "1.12.1", - options: { - appendTo: "body", - autoOpen: true, - buttons: [], - classes: { - "ui-dialog": "ui-corner-all", - "ui-dialog-titlebar": "ui-corner-all" - }, - closeOnEscape: true, - closeText: "Close", - draggable: true, - hide: null, - height: "auto", - maxHeight: null, - maxWidth: null, - minHeight: 150, - minWidth: 150, - modal: false, - position: { - my: "center", - at: "center", - of: window, - collision: "fit", - - // Ensure the titlebar is always visible - using: function( pos ) { - var topOffset = $( this ).css( pos ).offset().top; - if ( topOffset < 0 ) { - $( this ).css( "top", pos.top - topOffset ); - } - } - }, - resizable: true, - show: null, - title: null, - width: 300, - - // Callbacks - beforeClose: null, - close: null, - drag: null, - dragStart: null, - dragStop: null, - focus: null, - open: null, - resize: null, - resizeStart: null, - resizeStop: null - }, - - sizeRelatedOptions: { - buttons: true, - height: true, - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true, - width: true - }, - - resizableRelatedOptions: { - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true - }, - - _create: function() { - this.originalCss = { - display: this.element[ 0 ].style.display, - width: this.element[ 0 ].style.width, - minHeight: this.element[ 0 ].style.minHeight, - maxHeight: this.element[ 0 ].style.maxHeight, - height: this.element[ 0 ].style.height - }; - this.originalPosition = { - parent: this.element.parent(), - index: this.element.parent().children().index( this.element ) - }; - this.originalTitle = this.element.attr( "title" ); - if ( this.options.title == null && this.originalTitle != null ) { - this.options.title = this.originalTitle; - } - - // Dialogs can't be disabled - if ( this.options.disabled ) { - this.options.disabled = false; - } - - this._createWrapper(); - - this.element - .show() - .removeAttr( "title" ) - .appendTo( this.uiDialog ); - - this._addClass( "ui-dialog-content", "ui-widget-content" ); - - this._createTitlebar(); - this._createButtonPane(); - - if ( this.options.draggable && $.fn.draggable ) { - this._makeDraggable(); - } - if ( this.options.resizable && $.fn.resizable ) { - this._makeResizable(); - } - - this._isOpen = false; - - this._trackFocus(); - }, - - _init: function() { - if ( this.options.autoOpen ) { - this.open(); - } - }, - - _appendTo: function() { - var element = this.options.appendTo; - if ( element && ( element.jquery || element.nodeType ) ) { - return $( element ); - } - return this.document.find( element || "body" ).eq( 0 ); - }, - - _destroy: function() { - var next, - originalPosition = this.originalPosition; - - this._untrackInstance(); - this._destroyOverlay(); - - this.element - .removeUniqueId() - .css( this.originalCss ) - - // Without detaching first, the following becomes really slow - .detach(); - - this.uiDialog.remove(); - - if ( this.originalTitle ) { - this.element.attr( "title", this.originalTitle ); - } - - next = originalPosition.parent.children().eq( originalPosition.index ); - - // Don't try to place the dialog next to itself (#8613) - if ( next.length && next[ 0 ] !== this.element[ 0 ] ) { - next.before( this.element ); - } else { - originalPosition.parent.append( this.element ); - } - }, - - widget: function() { - return this.uiDialog; - }, - - disable: $.noop, - enable: $.noop, - - close: function( event ) { - var that = this; - - if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { - return; - } - - this._isOpen = false; - this._focusedElement = null; - this._destroyOverlay(); - this._untrackInstance(); - - if ( !this.opener.filter( ":focusable" ).trigger( "focus" ).length ) { - - // Hiding a focused element doesn't trigger blur in WebKit - // so in case we have nothing to focus on, explicitly blur the active element - // https://bugs.webkit.org/show_bug.cgi?id=47182 - $.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) ); - } - - this._hide( this.uiDialog, this.options.hide, function() { - that._trigger( "close", event ); - } ); - }, - - isOpen: function() { - return this._isOpen; - }, - - moveToTop: function() { - this._moveToTop(); - }, - - _moveToTop: function( event, silent ) { - var moved = false, - zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map( function() { - return +$( this ).css( "z-index" ); - } ).get(), - zIndexMax = Math.max.apply( null, zIndices ); - - if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) { - this.uiDialog.css( "z-index", zIndexMax + 1 ); - moved = true; - } - - if ( moved && !silent ) { - this._trigger( "focus", event ); - } - return moved; - }, - - open: function() { - var that = this; - if ( this._isOpen ) { - if ( this._moveToTop() ) { - this._focusTabbable(); - } - return; - } - - this._isOpen = true; - this.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); - - this._size(); - this._position(); - this._createOverlay(); - this._moveToTop( null, true ); - - // Ensure the overlay is moved to the top with the dialog, but only when - // opening. The overlay shouldn't move after the dialog is open so that - // modeless dialogs opened after the modal dialog stack properly. - if ( this.overlay ) { - this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 ); - } - - this._show( this.uiDialog, this.options.show, function() { - that._focusTabbable(); - that._trigger( "focus" ); - } ); - - // Track the dialog immediately upon openening in case a focus event - // somehow occurs outside of the dialog before an element inside the - // dialog is focused (#10152) - this._makeFocusTarget(); - - this._trigger( "open" ); - }, - - _focusTabbable: function() { - - // Set focus to the first match: - // 1. An element that was focused previously - // 2. First element inside the dialog matching [autofocus] - // 3. Tabbable element inside the content element - // 4. Tabbable element inside the buttonpane - // 5. The close button - // 6. The dialog itself - var hasFocus = this._focusedElement; - if ( !hasFocus ) { - hasFocus = this.element.find( "[autofocus]" ); - } - if ( !hasFocus.length ) { - hasFocus = this.element.find( ":tabbable" ); - } - if ( !hasFocus.length ) { - hasFocus = this.uiDialogButtonPane.find( ":tabbable" ); - } - if ( !hasFocus.length ) { - hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" ); - } - if ( !hasFocus.length ) { - hasFocus = this.uiDialog; - } - hasFocus.eq( 0 ).trigger( "focus" ); - }, - - _keepFocus: function( event ) { - function checkFocus() { - var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), - isActive = this.uiDialog[ 0 ] === activeElement || - $.contains( this.uiDialog[ 0 ], activeElement ); - if ( !isActive ) { - this._focusTabbable(); - } - } - event.preventDefault(); - checkFocus.call( this ); - - // support: IE - // IE <= 8 doesn't prevent moving focus even with event.preventDefault() - // so we check again later - this._delay( checkFocus ); - }, - - _createWrapper: function() { - this.uiDialog = $( "<div>" ) - .hide() - .attr( { - - // Setting tabIndex makes the div focusable - tabIndex: -1, - role: "dialog" - } ) - .appendTo( this._appendTo() ); - - this._addClass( this.uiDialog, "ui-dialog", "ui-widget ui-widget-content ui-front" ); - this._on( this.uiDialog, { - keydown: function( event ) { - if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && - event.keyCode === $.ui.keyCode.ESCAPE ) { - event.preventDefault(); - this.close( event ); - return; - } - - // Prevent tabbing out of dialogs - if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) { - return; - } - var tabbables = this.uiDialog.find( ":tabbable" ), - first = tabbables.filter( ":first" ), - last = tabbables.filter( ":last" ); - - if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) && - !event.shiftKey ) { - this._delay( function() { - first.trigger( "focus" ); - } ); - event.preventDefault(); - } else if ( ( event.target === first[ 0 ] || - event.target === this.uiDialog[ 0 ] ) && event.shiftKey ) { - this._delay( function() { - last.trigger( "focus" ); - } ); - event.preventDefault(); - } - }, - mousedown: function( event ) { - if ( this._moveToTop( event ) ) { - this._focusTabbable(); - } - } - } ); - - // We assume that any existing aria-describedby attribute means - // that the dialog content is marked up properly - // otherwise we brute force the content as the description - if ( !this.element.find( "[aria-describedby]" ).length ) { - this.uiDialog.attr( { - "aria-describedby": this.element.uniqueId().attr( "id" ) - } ); - } - }, - - _createTitlebar: function() { - var uiDialogTitle; - - this.uiDialogTitlebar = $( "<div>" ); - this._addClass( this.uiDialogTitlebar, - "ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" ); - this._on( this.uiDialogTitlebar, { - mousedown: function( event ) { - - // Don't prevent click on close button (#8838) - // Focusing a dialog that is partially scrolled out of view - // causes the browser to scroll it into view, preventing the click event - if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) { - - // Dialog isn't getting focus when dragging (#8063) - this.uiDialog.trigger( "focus" ); - } - } - } ); - - // Support: IE - // Use type="button" to prevent enter keypresses in textboxes from closing the - // dialog in IE (#9312) - this.uiDialogTitlebarClose = $( "<button type='button'></button>" ) - .button( { - label: $( "<a>" ).text( this.options.closeText ).html(), - icon: "ui-icon-closethick", - showLabel: false - } ) - .appendTo( this.uiDialogTitlebar ); - - this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" ); - this._on( this.uiDialogTitlebarClose, { - click: function( event ) { - event.preventDefault(); - this.close( event ); - } - } ); - - uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar ); - this._addClass( uiDialogTitle, "ui-dialog-title" ); - this._title( uiDialogTitle ); - - this.uiDialogTitlebar.prependTo( this.uiDialog ); - - this.uiDialog.attr( { - "aria-labelledby": uiDialogTitle.attr( "id" ) - } ); - }, - - _title: function( title ) { - if ( this.options.title ) { - title.text( this.options.title ); - } else { - title.html( " " ); - } - }, - - _createButtonPane: function() { - this.uiDialogButtonPane = $( "<div>" ); - this._addClass( this.uiDialogButtonPane, "ui-dialog-buttonpane", - "ui-widget-content ui-helper-clearfix" ); - - this.uiButtonSet = $( "<div>" ) - .appendTo( this.uiDialogButtonPane ); - this._addClass( this.uiButtonSet, "ui-dialog-buttonset" ); - - this._createButtons(); - }, - - _createButtons: function() { - var that = this, - buttons = this.options.buttons; - - // If we already have a button pane, remove it - this.uiDialogButtonPane.remove(); - this.uiButtonSet.empty(); - - if ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) { - this._removeClass( this.uiDialog, "ui-dialog-buttons" ); - return; - } - - $.each( buttons, function( name, props ) { - var click, buttonOptions; - props = $.isFunction( props ) ? - { click: props, text: name } : - props; - - // Default to a non-submitting button - props = $.extend( { type: "button" }, props ); - - // Change the context for the click callback to be the main element - click = props.click; - buttonOptions = { - icon: props.icon, - iconPosition: props.iconPosition, - showLabel: props.showLabel, - - // Deprecated options - icons: props.icons, - text: props.text - }; - - delete props.click; - delete props.icon; - delete props.iconPosition; - delete props.showLabel; - - // Deprecated options - delete props.icons; - if ( typeof props.text === "boolean" ) { - delete props.text; - } - - $( "<button></button>", props ) - .button( buttonOptions ) - .appendTo( that.uiButtonSet ) - .on( "click", function() { - click.apply( that.element[ 0 ], arguments ); - } ); - } ); - this._addClass( this.uiDialog, "ui-dialog-buttons" ); - this.uiDialogButtonPane.appendTo( this.uiDialog ); - }, - - _makeDraggable: function() { - var that = this, - options = this.options; - - function filteredUi( ui ) { - return { - position: ui.position, - offset: ui.offset - }; - } - - this.uiDialog.draggable( { - cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", - handle: ".ui-dialog-titlebar", - containment: "document", - start: function( event, ui ) { - that._addClass( $( this ), "ui-dialog-dragging" ); - that._blockFrames(); - that._trigger( "dragStart", event, filteredUi( ui ) ); - }, - drag: function( event, ui ) { - that._trigger( "drag", event, filteredUi( ui ) ); - }, - stop: function( event, ui ) { - var left = ui.offset.left - that.document.scrollLeft(), - top = ui.offset.top - that.document.scrollTop(); - - options.position = { - my: "left top", - at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + - "top" + ( top >= 0 ? "+" : "" ) + top, - of: that.window - }; - that._removeClass( $( this ), "ui-dialog-dragging" ); - that._unblockFrames(); - that._trigger( "dragStop", event, filteredUi( ui ) ); - } - } ); - }, - - _makeResizable: function() { - var that = this, - options = this.options, - handles = options.resizable, - - // .ui-resizable has position: relative defined in the stylesheet - // but dialogs have to use absolute or fixed positioning - position = this.uiDialog.css( "position" ), - resizeHandles = typeof handles === "string" ? - handles : - "n,e,s,w,se,sw,ne,nw"; - - function filteredUi( ui ) { - return { - originalPosition: ui.originalPosition, - originalSize: ui.originalSize, - position: ui.position, - size: ui.size - }; - } - - this.uiDialog.resizable( { - cancel: ".ui-dialog-content", - containment: "document", - alsoResize: this.element, - maxWidth: options.maxWidth, - maxHeight: options.maxHeight, - minWidth: options.minWidth, - minHeight: this._minHeight(), - handles: resizeHandles, - start: function( event, ui ) { - that._addClass( $( this ), "ui-dialog-resizing" ); - that._blockFrames(); - that._trigger( "resizeStart", event, filteredUi( ui ) ); - }, - resize: function( event, ui ) { - that._trigger( "resize", event, filteredUi( ui ) ); - }, - stop: function( event, ui ) { - var offset = that.uiDialog.offset(), - left = offset.left - that.document.scrollLeft(), - top = offset.top - that.document.scrollTop(); - - options.height = that.uiDialog.height(); - options.width = that.uiDialog.width(); - options.position = { - my: "left top", - at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + - "top" + ( top >= 0 ? "+" : "" ) + top, - of: that.window - }; - that._removeClass( $( this ), "ui-dialog-resizing" ); - that._unblockFrames(); - that._trigger( "resizeStop", event, filteredUi( ui ) ); - } - } ) - .css( "position", position ); - }, - - _trackFocus: function() { - this._on( this.widget(), { - focusin: function( event ) { - this._makeFocusTarget(); - this._focusedElement = $( event.target ); - } - } ); - }, - - _makeFocusTarget: function() { - this._untrackInstance(); - this._trackingInstances().unshift( this ); - }, - - _untrackInstance: function() { - var instances = this._trackingInstances(), - exists = $.inArray( this, instances ); - if ( exists !== -1 ) { - instances.splice( exists, 1 ); - } - }, - - _trackingInstances: function() { - var instances = this.document.data( "ui-dialog-instances" ); - if ( !instances ) { - instances = []; - this.document.data( "ui-dialog-instances", instances ); - } - return instances; - }, - - _minHeight: function() { - var options = this.options; - - return options.height === "auto" ? - options.minHeight : - Math.min( options.minHeight, options.height ); - }, - - _position: function() { - - // Need to show the dialog to get the actual offset in the position plugin - var isVisible = this.uiDialog.is( ":visible" ); - if ( !isVisible ) { - this.uiDialog.show(); - } - this.uiDialog.position( this.options.position ); - if ( !isVisible ) { - this.uiDialog.hide(); - } - }, - - _setOptions: function( options ) { - var that = this, - resize = false, - resizableOptions = {}; - - $.each( options, function( key, value ) { - that._setOption( key, value ); - - if ( key in that.sizeRelatedOptions ) { - resize = true; - } - if ( key in that.resizableRelatedOptions ) { - resizableOptions[ key ] = value; - } - } ); - - if ( resize ) { - this._size(); - this._position(); - } - if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { - this.uiDialog.resizable( "option", resizableOptions ); - } - }, - - _setOption: function( key, value ) { - var isDraggable, isResizable, - uiDialog = this.uiDialog; - - if ( key === "disabled" ) { - return; - } - - this._super( key, value ); - - if ( key === "appendTo" ) { - this.uiDialog.appendTo( this._appendTo() ); - } - - if ( key === "buttons" ) { - this._createButtons(); - } - - if ( key === "closeText" ) { - this.uiDialogTitlebarClose.button( { - - // Ensure that we always pass a string - label: $( "<a>" ).text( "" + this.options.closeText ).html() - } ); - } - - if ( key === "draggable" ) { - isDraggable = uiDialog.is( ":data(ui-draggable)" ); - if ( isDraggable && !value ) { - uiDialog.draggable( "destroy" ); - } - - if ( !isDraggable && value ) { - this._makeDraggable(); - } - } - - if ( key === "position" ) { - this._position(); - } - - if ( key === "resizable" ) { - - // currently resizable, becoming non-resizable - isResizable = uiDialog.is( ":data(ui-resizable)" ); - if ( isResizable && !value ) { - uiDialog.resizable( "destroy" ); - } - - // Currently resizable, changing handles - if ( isResizable && typeof value === "string" ) { - uiDialog.resizable( "option", "handles", value ); - } - - // Currently non-resizable, becoming resizable - if ( !isResizable && value !== false ) { - this._makeResizable(); - } - } - - if ( key === "title" ) { - this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) ); - } - }, - - _size: function() { - - // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content - // divs will both have width and height set, so we need to reset them - var nonContentHeight, minContentHeight, maxContentHeight, - options = this.options; - - // Reset content sizing - this.element.show().css( { - width: "auto", - minHeight: 0, - maxHeight: "none", - height: 0 - } ); - - if ( options.minWidth > options.width ) { - options.width = options.minWidth; - } - - // Reset wrapper sizing - // determine the height of all the non-content elements - nonContentHeight = this.uiDialog.css( { - height: "auto", - width: options.width - } ) - .outerHeight(); - minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); - maxContentHeight = typeof options.maxHeight === "number" ? - Math.max( 0, options.maxHeight - nonContentHeight ) : - "none"; - - if ( options.height === "auto" ) { - this.element.css( { - minHeight: minContentHeight, - maxHeight: maxContentHeight, - height: "auto" - } ); - } else { - this.element.height( Math.max( 0, options.height - nonContentHeight ) ); - } - - if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { - this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); - } - }, - - _blockFrames: function() { - this.iframeBlocks = this.document.find( "iframe" ).map( function() { - var iframe = $( this ); - - return $( "<div>" ) - .css( { - position: "absolute", - width: iframe.outerWidth(), - height: iframe.outerHeight() - } ) - .appendTo( iframe.parent() ) - .offset( iframe.offset() )[ 0 ]; - } ); - }, - - _unblockFrames: function() { - if ( this.iframeBlocks ) { - this.iframeBlocks.remove(); - delete this.iframeBlocks; - } - }, - - _allowInteraction: function( event ) { - if ( $( event.target ).closest( ".ui-dialog" ).length ) { - return true; - } - - // TODO: Remove hack when datepicker implements - // the .ui-front logic (#8989) - return !!$( event.target ).closest( ".ui-datepicker" ).length; - }, - - _createOverlay: function() { - if ( !this.options.modal ) { - return; - } - - // We use a delay in case the overlay is created from an - // event that we're going to be cancelling (#2804) - var isOpening = true; - this._delay( function() { - isOpening = false; - } ); - - if ( !this.document.data( "ui-dialog-overlays" ) ) { - - // Prevent use of anchors and inputs - // Using _on() for an event handler shared across many instances is - // safe because the dialogs stack and must be closed in reverse order - this._on( this.document, { - focusin: function( event ) { - if ( isOpening ) { - return; - } - - if ( !this._allowInteraction( event ) ) { - event.preventDefault(); - this._trackingInstances()[ 0 ]._focusTabbable(); - } - } - } ); - } - - this.overlay = $( "<div>" ) - .appendTo( this._appendTo() ); - - this._addClass( this.overlay, null, "ui-widget-overlay ui-front" ); - this._on( this.overlay, { - mousedown: "_keepFocus" - } ); - this.document.data( "ui-dialog-overlays", - ( this.document.data( "ui-dialog-overlays" ) || 0 ) + 1 ); - }, - - _destroyOverlay: function() { - if ( !this.options.modal ) { - return; - } - - if ( this.overlay ) { - var overlays = this.document.data( "ui-dialog-overlays" ) - 1; - - if ( !overlays ) { - this._off( this.document, "focusin" ); - this.document.removeData( "ui-dialog-overlays" ); - } else { - this.document.data( "ui-dialog-overlays", overlays ); - } - - this.overlay.remove(); - this.overlay = null; - } - } -} ); + $.widget( "ui.dialog", { + version: "1.13.0", + options: { + appendTo: "body", + autoOpen: true, + buttons: [], + classes: { + "ui-dialog": "ui-corner-all", + "ui-dialog-titlebar": "ui-corner-all" + }, + closeOnEscape: true, + closeText: "Close", + draggable: true, + hide: null, + height: "auto", + maxHeight: null, + maxWidth: null, + minHeight: 150, + minWidth: 150, + modal: false, + position: { + my: "center", + at: "center", + of: window, + collision: "fit", + + // Ensure the titlebar is always visible + using: function( pos ) { + var topOffset = $( this ).css( pos ).offset().top; + if ( topOffset < 0 ) { + $( this ).css( "top", pos.top - topOffset ); + } + } + }, + resizable: true, + show: null, + title: null, + width: 300, + + // Callbacks + beforeClose: null, + close: null, + drag: null, + dragStart: null, + dragStop: null, + focus: null, + open: null, + resize: null, + resizeStart: null, + resizeStop: null + }, + + sizeRelatedOptions: { + buttons: true, + height: true, + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true, + width: true + }, + + resizableRelatedOptions: { + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true + }, + + _create: function() { + this.originalCss = { + display: this.element[ 0 ].style.display, + width: this.element[ 0 ].style.width, + minHeight: this.element[ 0 ].style.minHeight, + maxHeight: this.element[ 0 ].style.maxHeight, + height: this.element[ 0 ].style.height + }; + this.originalPosition = { + parent: this.element.parent(), + index: this.element.parent().children().index( this.element ) + }; + this.originalTitle = this.element.attr( "title" ); + if ( this.options.title == null && this.originalTitle != null ) { + this.options.title = this.originalTitle; + } + + // Dialogs can't be disabled + if ( this.options.disabled ) { + this.options.disabled = false; + } + + this._createWrapper(); + + this.element + .show() + .removeAttr( "title" ) + .appendTo( this.uiDialog ); + + this._addClass( "ui-dialog-content", "ui-widget-content" ); + + this._createTitlebar(); + this._createButtonPane(); + + if ( this.options.draggable && $.fn.draggable ) { + this._makeDraggable(); + } + if ( this.options.resizable && $.fn.resizable ) { + this._makeResizable(); + } + + this._isOpen = false; + + this._trackFocus(); + }, + + _init: function() { + if ( this.options.autoOpen ) { + this.open(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + if ( element && ( element.jquery || element.nodeType ) ) { + return $( element ); + } + return this.document.find( element || "body" ).eq( 0 ); + }, + + _destroy: function() { + var next, + originalPosition = this.originalPosition; + + this._untrackInstance(); + this._destroyOverlay(); + + this.element + .removeUniqueId() + .css( this.originalCss ) + + // Without detaching first, the following becomes really slow + .detach(); + + this.uiDialog.remove(); + + if ( this.originalTitle ) { + this.element.attr( "title", this.originalTitle ); + } + + next = originalPosition.parent.children().eq( originalPosition.index ); + + // Don't try to place the dialog next to itself (#8613) + if ( next.length && next[ 0 ] !== this.element[ 0 ] ) { + next.before( this.element ); + } else { + originalPosition.parent.append( this.element ); + } + }, + + widget: function() { + return this.uiDialog; + }, + + disable: $.noop, + enable: $.noop, + + close: function( event ) { + var that = this; + + if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { + return; + } + + this._isOpen = false; + this._focusedElement = null; + this._destroyOverlay(); + this._untrackInstance(); + + if ( !this.opener.filter( ":focusable" ).trigger( "focus" ).length ) { + + // Hiding a focused element doesn't trigger blur in WebKit + // so in case we have nothing to focus on, explicitly blur the active element + // https://bugs.webkit.org/show_bug.cgi?id=47182 + $.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) ); + } + + this._hide( this.uiDialog, this.options.hide, function() { + that._trigger( "close", event ); + } ); + }, + + isOpen: function() { + return this._isOpen; + }, + + moveToTop: function() { + this._moveToTop(); + }, + + _moveToTop: function( event, silent ) { + var moved = false, + zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map( function() { + return +$( this ).css( "z-index" ); + } ).get(), + zIndexMax = Math.max.apply( null, zIndices ); + + if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) { + this.uiDialog.css( "z-index", zIndexMax + 1 ); + moved = true; + } + + if ( moved && !silent ) { + this._trigger( "focus", event ); + } + return moved; + }, + + open: function() { + var that = this; + if ( this._isOpen ) { + if ( this._moveToTop() ) { + this._focusTabbable(); + } + return; + } + + this._isOpen = true; + this.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); + + this._size(); + this._position(); + this._createOverlay(); + this._moveToTop( null, true ); + + // Ensure the overlay is moved to the top with the dialog, but only when + // opening. The overlay shouldn't move after the dialog is open so that + // modeless dialogs opened after the modal dialog stack properly. + if ( this.overlay ) { + this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 ); + } + + this._show( this.uiDialog, this.options.show, function() { + that._focusTabbable(); + that._trigger( "focus" ); + } ); + + // Track the dialog immediately upon opening in case a focus event + // somehow occurs outside of the dialog before an element inside the + // dialog is focused (#10152) + this._makeFocusTarget(); + + this._trigger( "open" ); + }, + + _focusTabbable: function() { + + // Set focus to the first match: + // 1. An element that was focused previously + // 2. First element inside the dialog matching [autofocus] + // 3. Tabbable element inside the content element + // 4. Tabbable element inside the buttonpane + // 5. The close button + // 6. The dialog itself + var hasFocus = this._focusedElement; + if ( !hasFocus ) { + hasFocus = this.element.find( "[autofocus]" ); + } + if ( !hasFocus.length ) { + hasFocus = this.element.find( ":tabbable" ); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogButtonPane.find( ":tabbable" ); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" ); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialog; + } + hasFocus.eq( 0 ).trigger( "focus" ); + }, + + _restoreTabbableFocus: function() { + var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), + isActive = this.uiDialog[ 0 ] === activeElement || + $.contains( this.uiDialog[ 0 ], activeElement ); + if ( !isActive ) { + this._focusTabbable(); + } + }, + + _keepFocus: function( event ) { + event.preventDefault(); + this._restoreTabbableFocus(); + + // support: IE + // IE <= 8 doesn't prevent moving focus even with event.preventDefault() + // so we check again later + this._delay( this._restoreTabbableFocus ); + }, + + _createWrapper: function() { + this.uiDialog = $( "<div>" ) + .hide() + .attr( { + + // Setting tabIndex makes the div focusable + tabIndex: -1, + role: "dialog" + } ) + .appendTo( this._appendTo() ); + + this._addClass( this.uiDialog, "ui-dialog", "ui-widget ui-widget-content ui-front" ); + this._on( this.uiDialog, { + keydown: function( event ) { + if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE ) { + event.preventDefault(); + this.close( event ); + return; + } + + // Prevent tabbing out of dialogs + if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) { + return; + } + var tabbables = this.uiDialog.find( ":tabbable" ), + first = tabbables.first(), + last = tabbables.last(); + + if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) && + !event.shiftKey ) { + this._delay( function() { + first.trigger( "focus" ); + } ); + event.preventDefault(); + } else if ( ( event.target === first[ 0 ] || + event.target === this.uiDialog[ 0 ] ) && event.shiftKey ) { + this._delay( function() { + last.trigger( "focus" ); + } ); + event.preventDefault(); + } + }, + mousedown: function( event ) { + if ( this._moveToTop( event ) ) { + this._focusTabbable(); + } + } + } ); + + // We assume that any existing aria-describedby attribute means + // that the dialog content is marked up properly + // otherwise we brute force the content as the description + if ( !this.element.find( "[aria-describedby]" ).length ) { + this.uiDialog.attr( { + "aria-describedby": this.element.uniqueId().attr( "id" ) + } ); + } + }, + + _createTitlebar: function() { + var uiDialogTitle; + + this.uiDialogTitlebar = $( "<div>" ); + this._addClass( this.uiDialogTitlebar, + "ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" ); + this._on( this.uiDialogTitlebar, { + mousedown: function( event ) { + + // Don't prevent click on close button (#8838) + // Focusing a dialog that is partially scrolled out of view + // causes the browser to scroll it into view, preventing the click event + if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) { + + // Dialog isn't getting focus when dragging (#8063) + this.uiDialog.trigger( "focus" ); + } + } + } ); + + // Support: IE + // Use type="button" to prevent enter keypresses in textboxes from closing the + // dialog in IE (#9312) + this.uiDialogTitlebarClose = $( "<button type='button'></button>" ) + .button( { + label: $( "<a>" ).text( this.options.closeText ).html(), + icon: "ui-icon-closethick", + showLabel: false + } ) + .appendTo( this.uiDialogTitlebar ); + + this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" ); + this._on( this.uiDialogTitlebarClose, { + click: function( event ) { + event.preventDefault(); + this.close( event ); + } + } ); + + uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar ); + this._addClass( uiDialogTitle, "ui-dialog-title" ); + this._title( uiDialogTitle ); + + this.uiDialogTitlebar.prependTo( this.uiDialog ); + + this.uiDialog.attr( { + "aria-labelledby": uiDialogTitle.attr( "id" ) + } ); + }, + + _title: function( title ) { + if ( this.options.title ) { + title.text( this.options.title ); + } else { + title.html( " " ); + } + }, + + _createButtonPane: function() { + this.uiDialogButtonPane = $( "<div>" ); + this._addClass( this.uiDialogButtonPane, "ui-dialog-buttonpane", + "ui-widget-content ui-helper-clearfix" ); + + this.uiButtonSet = $( "<div>" ) + .appendTo( this.uiDialogButtonPane ); + this._addClass( this.uiButtonSet, "ui-dialog-buttonset" ); + + this._createButtons(); + }, + + _createButtons: function() { + var that = this, + buttons = this.options.buttons; + + // If we already have a button pane, remove it + this.uiDialogButtonPane.remove(); + this.uiButtonSet.empty(); + + if ( $.isEmptyObject( buttons ) || ( Array.isArray( buttons ) && !buttons.length ) ) { + this._removeClass( this.uiDialog, "ui-dialog-buttons" ); + return; + } + + $.each( buttons, function( name, props ) { + var click, buttonOptions; + props = typeof props === "function" ? + { click: props, text: name } : + props; + + // Default to a non-submitting button + props = $.extend( { type: "button" }, props ); + + // Change the context for the click callback to be the main element + click = props.click; + buttonOptions = { + icon: props.icon, + iconPosition: props.iconPosition, + showLabel: props.showLabel, + + // Deprecated options + icons: props.icons, + text: props.text + }; + + delete props.click; + delete props.icon; + delete props.iconPosition; + delete props.showLabel; + + // Deprecated options + delete props.icons; + if ( typeof props.text === "boolean" ) { + delete props.text; + } + + $( "<button></button>", props ) + .button( buttonOptions ) + .appendTo( that.uiButtonSet ) + .on( "click", function() { + click.apply( that.element[ 0 ], arguments ); + } ); + } ); + this._addClass( this.uiDialog, "ui-dialog-buttons" ); + this.uiDialogButtonPane.appendTo( this.uiDialog ); + }, + + _makeDraggable: function() { + var that = this, + options = this.options; + + function filteredUi( ui ) { + return { + position: ui.position, + offset: ui.offset + }; + } + + this.uiDialog.draggable( { + cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", + handle: ".ui-dialog-titlebar", + containment: "document", + start: function( event, ui ) { + that._addClass( $( this ), "ui-dialog-dragging" ); + that._blockFrames(); + that._trigger( "dragStart", event, filteredUi( ui ) ); + }, + drag: function( event, ui ) { + that._trigger( "drag", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + var left = ui.offset.left - that.document.scrollLeft(), + top = ui.offset.top - that.document.scrollTop(); + + options.position = { + my: "left top", + at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + + "top" + ( top >= 0 ? "+" : "" ) + top, + of: that.window + }; + that._removeClass( $( this ), "ui-dialog-dragging" ); + that._unblockFrames(); + that._trigger( "dragStop", event, filteredUi( ui ) ); + } + } ); + }, + + _makeResizable: function() { + var that = this, + options = this.options, + handles = options.resizable, + + // .ui-resizable has position: relative defined in the stylesheet + // but dialogs have to use absolute or fixed positioning + position = this.uiDialog.css( "position" ), + resizeHandles = typeof handles === "string" ? + handles : + "n,e,s,w,se,sw,ne,nw"; + + function filteredUi( ui ) { + return { + originalPosition: ui.originalPosition, + originalSize: ui.originalSize, + position: ui.position, + size: ui.size + }; + } + + this.uiDialog.resizable( { + cancel: ".ui-dialog-content", + containment: "document", + alsoResize: this.element, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + minWidth: options.minWidth, + minHeight: this._minHeight(), + handles: resizeHandles, + start: function( event, ui ) { + that._addClass( $( this ), "ui-dialog-resizing" ); + that._blockFrames(); + that._trigger( "resizeStart", event, filteredUi( ui ) ); + }, + resize: function( event, ui ) { + that._trigger( "resize", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + var offset = that.uiDialog.offset(), + left = offset.left - that.document.scrollLeft(), + top = offset.top - that.document.scrollTop(); + + options.height = that.uiDialog.height(); + options.width = that.uiDialog.width(); + options.position = { + my: "left top", + at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + + "top" + ( top >= 0 ? "+" : "" ) + top, + of: that.window + }; + that._removeClass( $( this ), "ui-dialog-resizing" ); + that._unblockFrames(); + that._trigger( "resizeStop", event, filteredUi( ui ) ); + } + } ) + .css( "position", position ); + }, + + _trackFocus: function() { + this._on( this.widget(), { + focusin: function( event ) { + this._makeFocusTarget(); + this._focusedElement = $( event.target ); + } + } ); + }, + + _makeFocusTarget: function() { + this._untrackInstance(); + this._trackingInstances().unshift( this ); + }, + + _untrackInstance: function() { + var instances = this._trackingInstances(), + exists = $.inArray( this, instances ); + if ( exists !== -1 ) { + instances.splice( exists, 1 ); + } + }, + + _trackingInstances: function() { + var instances = this.document.data( "ui-dialog-instances" ); + if ( !instances ) { + instances = []; + this.document.data( "ui-dialog-instances", instances ); + } + return instances; + }, + + _minHeight: function() { + var options = this.options; + + return options.height === "auto" ? + options.minHeight : + Math.min( options.minHeight, options.height ); + }, + + _position: function() { + + // Need to show the dialog to get the actual offset in the position plugin + var isVisible = this.uiDialog.is( ":visible" ); + if ( !isVisible ) { + this.uiDialog.show(); + } + this.uiDialog.position( this.options.position ); + if ( !isVisible ) { + this.uiDialog.hide(); + } + }, + + _setOptions: function( options ) { + var that = this, + resize = false, + resizableOptions = {}; + + $.each( options, function( key, value ) { + that._setOption( key, value ); + + if ( key in that.sizeRelatedOptions ) { + resize = true; + } + if ( key in that.resizableRelatedOptions ) { + resizableOptions[ key ] = value; + } + } ); + + if ( resize ) { + this._size(); + this._position(); + } + if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { + this.uiDialog.resizable( "option", resizableOptions ); + } + }, + + _setOption: function( key, value ) { + var isDraggable, isResizable, + uiDialog = this.uiDialog; + + if ( key === "disabled" ) { + return; + } + + this._super( key, value ); + + if ( key === "appendTo" ) { + this.uiDialog.appendTo( this._appendTo() ); + } + + if ( key === "buttons" ) { + this._createButtons(); + } + + if ( key === "closeText" ) { + this.uiDialogTitlebarClose.button( { + + // Ensure that we always pass a string + label: $( "<a>" ).text( "" + this.options.closeText ).html() + } ); + } + + if ( key === "draggable" ) { + isDraggable = uiDialog.is( ":data(ui-draggable)" ); + if ( isDraggable && !value ) { + uiDialog.draggable( "destroy" ); + } + + if ( !isDraggable && value ) { + this._makeDraggable(); + } + } + + if ( key === "position" ) { + this._position(); + } + + if ( key === "resizable" ) { + + // currently resizable, becoming non-resizable + isResizable = uiDialog.is( ":data(ui-resizable)" ); + if ( isResizable && !value ) { + uiDialog.resizable( "destroy" ); + } + + // Currently resizable, changing handles + if ( isResizable && typeof value === "string" ) { + uiDialog.resizable( "option", "handles", value ); + } + + // Currently non-resizable, becoming resizable + if ( !isResizable && value !== false ) { + this._makeResizable(); + } + } + + if ( key === "title" ) { + this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) ); + } + }, + + _size: function() { + + // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content + // divs will both have width and height set, so we need to reset them + var nonContentHeight, minContentHeight, maxContentHeight, + options = this.options; + + // Reset content sizing + this.element.show().css( { + width: "auto", + minHeight: 0, + maxHeight: "none", + height: 0 + } ); + + if ( options.minWidth > options.width ) { + options.width = options.minWidth; + } + + // Reset wrapper sizing + // determine the height of all the non-content elements + nonContentHeight = this.uiDialog.css( { + height: "auto", + width: options.width + } ) + .outerHeight(); + minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); + maxContentHeight = typeof options.maxHeight === "number" ? + Math.max( 0, options.maxHeight - nonContentHeight ) : + "none"; + + if ( options.height === "auto" ) { + this.element.css( { + minHeight: minContentHeight, + maxHeight: maxContentHeight, + height: "auto" + } ); + } else { + this.element.height( Math.max( 0, options.height - nonContentHeight ) ); + } + + if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { + this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); + } + }, + + _blockFrames: function() { + this.iframeBlocks = this.document.find( "iframe" ).map( function() { + var iframe = $( this ); + + return $( "<div>" ) + .css( { + position: "absolute", + width: iframe.outerWidth(), + height: iframe.outerHeight() + } ) + .appendTo( iframe.parent() ) + .offset( iframe.offset() )[ 0 ]; + } ); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _allowInteraction: function( event ) { + if ( $( event.target ).closest( ".ui-dialog" ).length ) { + return true; + } + + // TODO: Remove hack when datepicker implements + // the .ui-front logic (#8989) + return !!$( event.target ).closest( ".ui-datepicker" ).length; + }, + + _createOverlay: function() { + if ( !this.options.modal ) { + return; + } + + var jqMinor = $.fn.jquery.substring( 0, 4 ); + + // We use a delay in case the overlay is created from an + // event that we're going to be cancelling (#2804) + var isOpening = true; + this._delay( function() { + isOpening = false; + } ); + + if ( !this.document.data( "ui-dialog-overlays" ) ) { + + // Prevent use of anchors and inputs + // This doesn't use `_on()` because it is a shared event handler + // across all open modal dialogs. + this.document.on( "focusin.ui-dialog", function( event ) { + if ( isOpening ) { + return; + } + + var instance = this._trackingInstances()[ 0 ]; + if ( !instance._allowInteraction( event ) ) { + event.preventDefault(); + instance._focusTabbable(); + + // Support: jQuery >=3.4 <3.6 only + // Focus re-triggering in jQuery 3.4/3.5 makes the original element + // have its focus event propagated last, breaking the re-targeting. + // Trigger focus in a delay in addition if needed to avoid the issue + // See https://github.com/jquery/jquery/issues/4382 + if ( jqMinor === "3.4." || jqMinor === "3.5." ) { + instance._delay( instance._restoreTabbableFocus ); + } + } + }.bind( this ) ); + } + + this.overlay = $( "<div>" ) + .appendTo( this._appendTo() ); + + this._addClass( this.overlay, null, "ui-widget-overlay ui-front" ); + this._on( this.overlay, { + mousedown: "_keepFocus" + } ); + this.document.data( "ui-dialog-overlays", + ( this.document.data( "ui-dialog-overlays" ) || 0 ) + 1 ); + }, + + _destroyOverlay: function() { + if ( !this.options.modal ) { + return; + } + + if ( this.overlay ) { + var overlays = this.document.data( "ui-dialog-overlays" ) - 1; + + if ( !overlays ) { + this.document.off( "focusin.ui-dialog" ); + this.document.removeData( "ui-dialog-overlays" ); + } else { + this.document.data( "ui-dialog-overlays", overlays ); + } + + this.overlay.remove(); + this.overlay = null; + } + } + } ); // DEPRECATED // TODO: switch return back to widget declaration at top of file when this is removed -if ( $.uiBackCompat !== false ) { - - // Backcompat for dialogClass option - $.widget( "ui.dialog", $.ui.dialog, { - options: { - dialogClass: "" - }, - _createWrapper: function() { - this._super(); - this.uiDialog.addClass( this.options.dialogClass ); - }, - _setOption: function( key, value ) { - if ( key === "dialogClass" ) { - this.uiDialog - .removeClass( this.options.dialogClass ) - .addClass( value ); - } - this._superApply( arguments ); - } - } ); -} - -var widgetsDialog = $.ui.dialog; - - -/*! - * jQuery UI Progressbar 1.12.1 + if ( $.uiBackCompat !== false ) { + + // Backcompat for dialogClass option + $.widget( "ui.dialog", $.ui.dialog, { + options: { + dialogClass: "" + }, + _createWrapper: function() { + this._super(); + this.uiDialog.addClass( this.options.dialogClass ); + }, + _setOption: function( key, value ) { + if ( key === "dialogClass" ) { + this.uiDialog + .removeClass( this.options.dialogClass ) + .addClass( value ); + } + this._superApply( arguments ); + } + } ); + } + + var widgetsDialog = $.ui.dialog; + + + /*! + * jQuery UI Progressbar 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -12605,9 +12909,9 @@ var widgetsDialog = $.ui.dialog; //>>label: Progressbar //>>group: Widgets -// jscs:disable maximumLineLength + /* eslint-disable max-len */ //>>description: Displays a status indicator for loading state, standard percentage, and other progress indicators. -// jscs:enable maximumLineLength + /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/progressbar/ //>>demos: http://jqueryui.com/progressbar/ //>>css.structure: ../../themes/base/core.css @@ -12615,151 +12919,150 @@ var widgetsDialog = $.ui.dialog; //>>css.theme: ../../themes/base/theme.css + var widgetsProgressbar = $.widget( "ui.progressbar", { + version: "1.13.0", + options: { + classes: { + "ui-progressbar": "ui-corner-all", + "ui-progressbar-value": "ui-corner-left", + "ui-progressbar-complete": "ui-corner-right" + }, + max: 100, + value: 0, -var widgetsProgressbar = $.widget( "ui.progressbar", { - version: "1.12.1", - options: { - classes: { - "ui-progressbar": "ui-corner-all", - "ui-progressbar-value": "ui-corner-left", - "ui-progressbar-complete": "ui-corner-right" - }, - max: 100, - value: 0, - - change: null, - complete: null - }, - - min: 0, + change: null, + complete: null + }, - _create: function() { + min: 0, - // Constrain initial value - this.oldValue = this.options.value = this._constrainedValue(); - - this.element.attr( { - - // Only set static values; aria-valuenow and aria-valuemax are - // set inside _refreshValue() - role: "progressbar", - "aria-valuemin": this.min - } ); - this._addClass( "ui-progressbar", "ui-widget ui-widget-content" ); - - this.valueDiv = $( "<div>" ).appendTo( this.element ); - this._addClass( this.valueDiv, "ui-progressbar-value", "ui-widget-header" ); - this._refreshValue(); - }, - - _destroy: function() { - this.element.removeAttr( "role aria-valuemin aria-valuemax aria-valuenow" ); - - this.valueDiv.remove(); - }, - - value: function( newValue ) { - if ( newValue === undefined ) { - return this.options.value; - } - - this.options.value = this._constrainedValue( newValue ); - this._refreshValue(); - }, - - _constrainedValue: function( newValue ) { - if ( newValue === undefined ) { - newValue = this.options.value; - } - - this.indeterminate = newValue === false; - - // Sanitize value - if ( typeof newValue !== "number" ) { - newValue = 0; - } - - return this.indeterminate ? false : - Math.min( this.options.max, Math.max( this.min, newValue ) ); - }, - - _setOptions: function( options ) { - - // Ensure "value" option is set after other values (like max) - var value = options.value; - delete options.value; - - this._super( options ); - - this.options.value = this._constrainedValue( value ); - this._refreshValue(); - }, - - _setOption: function( key, value ) { - if ( key === "max" ) { - - // Don't allow a max less than min - value = Math.max( this.min, value ); - } - this._super( key, value ); - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this.element.attr( "aria-disabled", value ); - this._toggleClass( null, "ui-state-disabled", !!value ); - }, - - _percentage: function() { - return this.indeterminate ? - 100 : - 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); - }, - - _refreshValue: function() { - var value = this.options.value, - percentage = this._percentage(); - - this.valueDiv - .toggle( this.indeterminate || value > this.min ) - .width( percentage.toFixed( 0 ) + "%" ); - - this - ._toggleClass( this.valueDiv, "ui-progressbar-complete", null, - value === this.options.max ) - ._toggleClass( "ui-progressbar-indeterminate", null, this.indeterminate ); - - if ( this.indeterminate ) { - this.element.removeAttr( "aria-valuenow" ); - if ( !this.overlayDiv ) { - this.overlayDiv = $( "<div>" ).appendTo( this.valueDiv ); - this._addClass( this.overlayDiv, "ui-progressbar-overlay" ); - } - } else { - this.element.attr( { - "aria-valuemax": this.options.max, - "aria-valuenow": value - } ); - if ( this.overlayDiv ) { - this.overlayDiv.remove(); - this.overlayDiv = null; - } - } - - if ( this.oldValue !== value ) { - this.oldValue = value; - this._trigger( "change" ); - } - if ( value === this.options.max ) { - this._trigger( "complete" ); - } - } -} ); + _create: function() { - -/*! - * jQuery UI Selectmenu 1.12.1 + // Constrain initial value + this.oldValue = this.options.value = this._constrainedValue(); + + this.element.attr( { + + // Only set static values; aria-valuenow and aria-valuemax are + // set inside _refreshValue() + role: "progressbar", + "aria-valuemin": this.min + } ); + this._addClass( "ui-progressbar", "ui-widget ui-widget-content" ); + + this.valueDiv = $( "<div>" ).appendTo( this.element ); + this._addClass( this.valueDiv, "ui-progressbar-value", "ui-widget-header" ); + this._refreshValue(); + }, + + _destroy: function() { + this.element.removeAttr( "role aria-valuemin aria-valuemax aria-valuenow" ); + + this.valueDiv.remove(); + }, + + value: function( newValue ) { + if ( newValue === undefined ) { + return this.options.value; + } + + this.options.value = this._constrainedValue( newValue ); + this._refreshValue(); + }, + + _constrainedValue: function( newValue ) { + if ( newValue === undefined ) { + newValue = this.options.value; + } + + this.indeterminate = newValue === false; + + // Sanitize value + if ( typeof newValue !== "number" ) { + newValue = 0; + } + + return this.indeterminate ? false : + Math.min( this.options.max, Math.max( this.min, newValue ) ); + }, + + _setOptions: function( options ) { + + // Ensure "value" option is set after other values (like max) + var value = options.value; + delete options.value; + + this._super( options ); + + this.options.value = this._constrainedValue( value ); + this._refreshValue(); + }, + + _setOption: function( key, value ) { + if ( key === "max" ) { + + // Don't allow a max less than min + value = Math.max( this.min, value ); + } + this._super( key, value ); + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this.element.attr( "aria-disabled", value ); + this._toggleClass( null, "ui-state-disabled", !!value ); + }, + + _percentage: function() { + return this.indeterminate ? + 100 : + 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); + }, + + _refreshValue: function() { + var value = this.options.value, + percentage = this._percentage(); + + this.valueDiv + .toggle( this.indeterminate || value > this.min ) + .width( percentage.toFixed( 0 ) + "%" ); + + this + ._toggleClass( this.valueDiv, "ui-progressbar-complete", null, + value === this.options.max ) + ._toggleClass( "ui-progressbar-indeterminate", null, this.indeterminate ); + + if ( this.indeterminate ) { + this.element.removeAttr( "aria-valuenow" ); + if ( !this.overlayDiv ) { + this.overlayDiv = $( "<div>" ).appendTo( this.valueDiv ); + this._addClass( this.overlayDiv, "ui-progressbar-overlay" ); + } + } else { + this.element.attr( { + "aria-valuemax": this.options.max, + "aria-valuenow": value + } ); + if ( this.overlayDiv ) { + this.overlayDiv.remove(); + this.overlayDiv = null; + } + } + + if ( this.oldValue !== value ) { + this.oldValue = value; + this._trigger( "change" ); + } + if ( value === this.options.max ) { + this._trigger( "complete" ); + } + } + } ); + + + /*! + * jQuery UI Selectmenu 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -12769,9 +13072,9 @@ var widgetsProgressbar = $.widget( "ui.progressbar", { //>>label: Selectmenu //>>group: Widgets -// jscs:disable maximumLineLength + /* eslint-disable max-len */ //>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select. -// jscs:enable maximumLineLength + /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/selectmenu/ //>>demos: http://jqueryui.com/selectmenu/ //>>css.structure: ../../themes/base/core.css @@ -12779,653 +13082,656 @@ var widgetsProgressbar = $.widget( "ui.progressbar", { //>>css.theme: ../../themes/base/theme.css - -var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { - version: "1.12.1", - defaultElement: "<select>", - options: { - appendTo: null, - classes: { - "ui-selectmenu-button-open": "ui-corner-top", - "ui-selectmenu-button-closed": "ui-corner-all" - }, - disabled: null, - icons: { - button: "ui-icon-triangle-1-s" - }, - position: { - my: "left top", - at: "left bottom", - collision: "none" - }, - width: false, - - // Callbacks - change: null, - close: null, - focus: null, - open: null, - select: null - }, - - _create: function() { - var selectmenuId = this.element.uniqueId().attr( "id" ); - this.ids = { - element: selectmenuId, - button: selectmenuId + "-button", - menu: selectmenuId + "-menu" - }; - - this._drawButton(); - this._drawMenu(); - this._bindFormResetHandler(); - - this._rendered = false; - this.menuItems = $(); - }, - - _drawButton: function() { - var icon, - that = this, - item = this._parseOption( - this.element.find( "option:selected" ), - this.element[ 0 ].selectedIndex - ); - - // Associate existing label with the new button - this.labels = this.element.labels().attr( "for", this.ids.button ); - this._on( this.labels, { - click: function( event ) { - this.button.focus(); - event.preventDefault(); - } - } ); - - // Hide original select element - this.element.hide(); - - // Create button - this.button = $( "<span>", { - tabindex: this.options.disabled ? -1 : 0, - id: this.ids.button, - role: "combobox", - "aria-expanded": "false", - "aria-autocomplete": "list", - "aria-owns": this.ids.menu, - "aria-haspopup": "true", - title: this.element.attr( "title" ) - } ) - .insertAfter( this.element ); - - this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed", - "ui-button ui-widget" ); - - icon = $( "<span>" ).appendTo( this.button ); - this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button ); - this.buttonItem = this._renderButtonItem( item ) - .appendTo( this.button ); - - if ( this.options.width !== false ) { - this._resizeButton(); - } - - this._on( this.button, this._buttonEvents ); - this.button.one( "focusin", function() { - - // Delay rendering the menu items until the button receives focus. - // The menu may have already been rendered via a programmatic open. - if ( !that._rendered ) { - that._refreshMenu(); - } - } ); - }, - - _drawMenu: function() { - var that = this; - - // Create menu - this.menu = $( "<ul>", { - "aria-hidden": "true", - "aria-labelledby": this.ids.button, - id: this.ids.menu - } ); - - // Wrap menu - this.menuWrap = $( "<div>" ).append( this.menu ); - this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" ); - this.menuWrap.appendTo( this._appendTo() ); - - // Initialize menu widget - this.menuInstance = this.menu - .menu( { - classes: { - "ui-menu": "ui-corner-bottom" - }, - role: "listbox", - select: function( event, ui ) { - event.preventDefault(); - - // Support: IE8 - // If the item was selected via a click, the text selection - // will be destroyed in IE - that._setSelection(); - - that._select( ui.item.data( "ui-selectmenu-item" ), event ); - }, - focus: function( event, ui ) { - var item = ui.item.data( "ui-selectmenu-item" ); - - // Prevent inital focus from firing and check if its a newly focused item - if ( that.focusIndex != null && item.index !== that.focusIndex ) { - that._trigger( "focus", event, { item: item } ); - if ( !that.isOpen ) { - that._select( item, event ); - } - } - that.focusIndex = item.index; - - that.button.attr( "aria-activedescendant", - that.menuItems.eq( item.index ).attr( "id" ) ); - } - } ) - .menu( "instance" ); - - // Don't close the menu on mouseleave - this.menuInstance._off( this.menu, "mouseleave" ); - - // Cancel the menu's collapseAll on document click - this.menuInstance._closeOnDocumentClick = function() { - return false; - }; - - // Selects often contain empty items, but never contain dividers - this.menuInstance._isDivider = function() { - return false; - }; - }, - - refresh: function() { - this._refreshMenu(); - this.buttonItem.replaceWith( - this.buttonItem = this._renderButtonItem( - - // Fall back to an empty object in case there are no options - this._getSelectedItem().data( "ui-selectmenu-item" ) || {} - ) - ); - if ( this.options.width === null ) { - this._resizeButton(); - } - }, - - _refreshMenu: function() { - var item, - options = this.element.find( "option" ); - - this.menu.empty(); - - this._parseOptions( options ); - this._renderMenu( this.menu, this.items ); - - this.menuInstance.refresh(); - this.menuItems = this.menu.find( "li" ) - .not( ".ui-selectmenu-optgroup" ) - .find( ".ui-menu-item-wrapper" ); - - this._rendered = true; - - if ( !options.length ) { - return; - } - - item = this._getSelectedItem(); - - // Update the menu to have the correct item focused - this.menuInstance.focus( null, item ); - this._setAria( item.data( "ui-selectmenu-item" ) ); - - // Set disabled state - this._setOption( "disabled", this.element.prop( "disabled" ) ); - }, - - open: function( event ) { - if ( this.options.disabled ) { - return; - } - - // If this is the first time the menu is being opened, render the items - if ( !this._rendered ) { - this._refreshMenu(); - } else { - - // Menu clears focus on close, reset focus to selected item - this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" ); - this.menuInstance.focus( null, this._getSelectedItem() ); - } - - // If there are no options, don't open the menu - if ( !this.menuItems.length ) { - return; - } - - this.isOpen = true; - this._toggleAttr(); - this._resizeMenu(); - this._position(); - - this._on( this.document, this._documentClick ); - - this._trigger( "open", event ); - }, - - _position: function() { - this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) ); - }, - - close: function( event ) { - if ( !this.isOpen ) { - return; - } - - this.isOpen = false; - this._toggleAttr(); - - this.range = null; - this._off( this.document ); - - this._trigger( "close", event ); - }, - - widget: function() { - return this.button; - }, - - menuWidget: function() { - return this.menu; - }, - - _renderButtonItem: function( item ) { - var buttonItem = $( "<span>" ); - - this._setText( buttonItem, item.label ); - this._addClass( buttonItem, "ui-selectmenu-text" ); - - return buttonItem; - }, - - _renderMenu: function( ul, items ) { - var that = this, - currentOptgroup = ""; - - $.each( items, function( index, item ) { - var li; - - if ( item.optgroup !== currentOptgroup ) { - li = $( "<li>", { - text: item.optgroup - } ); - that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" + - ( item.element.parent( "optgroup" ).prop( "disabled" ) ? - " ui-state-disabled" : - "" ) ); - - li.appendTo( ul ); - - currentOptgroup = item.optgroup; - } - - that._renderItemData( ul, item ); - } ); - }, - - _renderItemData: function( ul, item ) { - return this._renderItem( ul, item ).data( "ui-selectmenu-item", item ); - }, - - _renderItem: function( ul, item ) { - var li = $( "<li>" ), - wrapper = $( "<div>", { - title: item.element.attr( "title" ) - } ); - - if ( item.disabled ) { - this._addClass( li, null, "ui-state-disabled" ); - } - this._setText( wrapper, item.label ); - - return li.append( wrapper ).appendTo( ul ); - }, - - _setText: function( element, value ) { - if ( value ) { - element.text( value ); - } else { - element.html( " " ); - } - }, - - _move: function( direction, event ) { - var item, next, - filter = ".ui-menu-item"; - - if ( this.isOpen ) { - item = this.menuItems.eq( this.focusIndex ).parent( "li" ); - } else { - item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); - filter += ":not(.ui-state-disabled)"; - } - - if ( direction === "first" || direction === "last" ) { - next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 ); - } else { - next = item[ direction + "All" ]( filter ).eq( 0 ); - } - - if ( next.length ) { - this.menuInstance.focus( event, next ); - } - }, - - _getSelectedItem: function() { - return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); - }, - - _toggle: function( event ) { - this[ this.isOpen ? "close" : "open" ]( event ); - }, - - _setSelection: function() { - var selection; - - if ( !this.range ) { - return; - } - - if ( window.getSelection ) { - selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange( this.range ); - - // Support: IE8 - } else { - this.range.select(); - } - - // Support: IE - // Setting the text selection kills the button focus in IE, but - // restoring the focus doesn't kill the selection. - this.button.focus(); - }, - - _documentClick: { - mousedown: function( event ) { - if ( !this.isOpen ) { - return; - } - - if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + - $.ui.escapeSelector( this.ids.button ) ).length ) { - this.close( event ); - } - } - }, - - _buttonEvents: { - - // Prevent text selection from being reset when interacting with the selectmenu (#10144) - mousedown: function() { - var selection; - - if ( window.getSelection ) { - selection = window.getSelection(); - if ( selection.rangeCount ) { - this.range = selection.getRangeAt( 0 ); - } - - // Support: IE8 - } else { - this.range = document.selection.createRange(); - } - }, - - click: function( event ) { - this._setSelection(); - this._toggle( event ); - }, - - keydown: function( event ) { - var preventDefault = true; - switch ( event.keyCode ) { - case $.ui.keyCode.TAB: - case $.ui.keyCode.ESCAPE: - this.close( event ); - preventDefault = false; - break; - case $.ui.keyCode.ENTER: - if ( this.isOpen ) { - this._selectFocusedItem( event ); - } - break; - case $.ui.keyCode.UP: - if ( event.altKey ) { - this._toggle( event ); - } else { - this._move( "prev", event ); - } - break; - case $.ui.keyCode.DOWN: - if ( event.altKey ) { - this._toggle( event ); - } else { - this._move( "next", event ); - } - break; - case $.ui.keyCode.SPACE: - if ( this.isOpen ) { - this._selectFocusedItem( event ); - } else { - this._toggle( event ); - } - break; - case $.ui.keyCode.LEFT: - this._move( "prev", event ); - break; - case $.ui.keyCode.RIGHT: - this._move( "next", event ); - break; - case $.ui.keyCode.HOME: - case $.ui.keyCode.PAGE_UP: - this._move( "first", event ); - break; - case $.ui.keyCode.END: - case $.ui.keyCode.PAGE_DOWN: - this._move( "last", event ); - break; - default: - this.menu.trigger( event ); - preventDefault = false; - } - - if ( preventDefault ) { - event.preventDefault(); - } - } - }, - - _selectFocusedItem: function( event ) { - var item = this.menuItems.eq( this.focusIndex ).parent( "li" ); - if ( !item.hasClass( "ui-state-disabled" ) ) { - this._select( item.data( "ui-selectmenu-item" ), event ); - } - }, - - _select: function( item, event ) { - var oldIndex = this.element[ 0 ].selectedIndex; - - // Change native select element - this.element[ 0 ].selectedIndex = item.index; - this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) ); - this._setAria( item ); - this._trigger( "select", event, { item: item } ); - - if ( item.index !== oldIndex ) { - this._trigger( "change", event, { item: item } ); - } - - this.close( event ); - }, - - _setAria: function( item ) { - var id = this.menuItems.eq( item.index ).attr( "id" ); - - this.button.attr( { - "aria-labelledby": id, - "aria-activedescendant": id - } ); - this.menu.attr( "aria-activedescendant", id ); - }, - - _setOption: function( key, value ) { - if ( key === "icons" ) { - var icon = this.button.find( "span.ui-icon" ); - this._removeClass( icon, null, this.options.icons.button ) - ._addClass( icon, null, value.button ); - } - - this._super( key, value ); - - if ( key === "appendTo" ) { - this.menuWrap.appendTo( this._appendTo() ); - } - - if ( key === "width" ) { - this._resizeButton(); - } - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this.menuInstance.option( "disabled", value ); - this.button.attr( "aria-disabled", value ); - this._toggleClass( this.button, null, "ui-state-disabled", value ); - - this.element.prop( "disabled", value ); - if ( value ) { - this.button.attr( "tabindex", -1 ); - this.close(); - } else { - this.button.attr( "tabindex", 0 ); - } - }, - - _appendTo: function() { - var element = this.options.appendTo; - - if ( element ) { - element = element.jquery || element.nodeType ? - $( element ) : - this.document.find( element ).eq( 0 ); - } - - if ( !element || !element[ 0 ] ) { - element = this.element.closest( ".ui-front, dialog" ); - } - - if ( !element.length ) { - element = this.document[ 0 ].body; - } - - return element; - }, - - _toggleAttr: function() { - this.button.attr( "aria-expanded", this.isOpen ); - - // We can't use two _toggleClass() calls here, because we need to make sure - // we always remove classes first and add them second, otherwise if both classes have the - // same theme class, it will be removed after we add it. - this._removeClass( this.button, "ui-selectmenu-button-" + - ( this.isOpen ? "closed" : "open" ) ) - ._addClass( this.button, "ui-selectmenu-button-" + - ( this.isOpen ? "open" : "closed" ) ) - ._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen ); - - this.menu.attr( "aria-hidden", !this.isOpen ); - }, - - _resizeButton: function() { - var width = this.options.width; - - // For `width: false`, just remove inline style and stop - if ( width === false ) { - this.button.css( "width", "" ); - return; - } - - // For `width: null`, match the width of the original element - if ( width === null ) { - width = this.element.show().outerWidth(); - this.element.hide(); - } - - this.button.outerWidth( width ); - }, - - _resizeMenu: function() { - this.menu.outerWidth( Math.max( - this.button.outerWidth(), - - // Support: IE10 - // IE10 wraps long text (possibly a rounding bug) - // so we add 1px to avoid the wrapping - this.menu.width( "" ).outerWidth() + 1 - ) ); - }, - - _getCreateOptions: function() { - var options = this._super(); - - options.disabled = this.element.prop( "disabled" ); - - return options; - }, - - _parseOptions: function( options ) { - var that = this, - data = []; - options.each( function( index, item ) { - data.push( that._parseOption( $( item ), index ) ); - } ); - this.items = data; - }, - - _parseOption: function( option, index ) { - var optgroup = option.parent( "optgroup" ); - - return { - element: option, - index: index, - value: option.val(), - label: option.text(), - optgroup: optgroup.attr( "label" ) || "", - disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" ) - }; - }, - - _destroy: function() { - this._unbindFormResetHandler(); - this.menuWrap.remove(); - this.button.remove(); - this.element.show(); - this.element.removeUniqueId(); - this.labels.attr( "for", this.ids.element ); - } -} ] ); - - -/*! - * jQuery UI Slider 1.12.1 + var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { + version: "1.13.0", + defaultElement: "<select>", + options: { + appendTo: null, + classes: { + "ui-selectmenu-button-open": "ui-corner-top", + "ui-selectmenu-button-closed": "ui-corner-all" + }, + disabled: null, + icons: { + button: "ui-icon-triangle-1-s" + }, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + width: false, + + // Callbacks + change: null, + close: null, + focus: null, + open: null, + select: null + }, + + _create: function() { + var selectmenuId = this.element.uniqueId().attr( "id" ); + this.ids = { + element: selectmenuId, + button: selectmenuId + "-button", + menu: selectmenuId + "-menu" + }; + + this._drawButton(); + this._drawMenu(); + this._bindFormResetHandler(); + + this._rendered = false; + this.menuItems = $(); + }, + + _drawButton: function() { + var icon, + that = this, + item = this._parseOption( + this.element.find( "option:selected" ), + this.element[ 0 ].selectedIndex + ); + + // Associate existing label with the new button + this.labels = this.element.labels().attr( "for", this.ids.button ); + this._on( this.labels, { + click: function( event ) { + this.button.trigger( "focus" ); + event.preventDefault(); + } + } ); + + // Hide original select element + this.element.hide(); + + // Create button + this.button = $( "<span>", { + tabindex: this.options.disabled ? -1 : 0, + id: this.ids.button, + role: "combobox", + "aria-expanded": "false", + "aria-autocomplete": "list", + "aria-owns": this.ids.menu, + "aria-haspopup": "true", + title: this.element.attr( "title" ) + } ) + .insertAfter( this.element ); + + this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed", + "ui-button ui-widget" ); + + icon = $( "<span>" ).appendTo( this.button ); + this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button ); + this.buttonItem = this._renderButtonItem( item ) + .appendTo( this.button ); + + if ( this.options.width !== false ) { + this._resizeButton(); + } + + this._on( this.button, this._buttonEvents ); + this.button.one( "focusin", function() { + + // Delay rendering the menu items until the button receives focus. + // The menu may have already been rendered via a programmatic open. + if ( !that._rendered ) { + that._refreshMenu(); + } + } ); + }, + + _drawMenu: function() { + var that = this; + + // Create menu + this.menu = $( "<ul>", { + "aria-hidden": "true", + "aria-labelledby": this.ids.button, + id: this.ids.menu + } ); + + // Wrap menu + this.menuWrap = $( "<div>" ).append( this.menu ); + this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" ); + this.menuWrap.appendTo( this._appendTo() ); + + // Initialize menu widget + this.menuInstance = this.menu + .menu( { + classes: { + "ui-menu": "ui-corner-bottom" + }, + role: "listbox", + select: function( event, ui ) { + event.preventDefault(); + + // Support: IE8 + // If the item was selected via a click, the text selection + // will be destroyed in IE + that._setSelection(); + + that._select( ui.item.data( "ui-selectmenu-item" ), event ); + }, + focus: function( event, ui ) { + var item = ui.item.data( "ui-selectmenu-item" ); + + // Prevent inital focus from firing and check if its a newly focused item + if ( that.focusIndex != null && item.index !== that.focusIndex ) { + that._trigger( "focus", event, { item: item } ); + if ( !that.isOpen ) { + that._select( item, event ); + } + } + that.focusIndex = item.index; + + that.button.attr( "aria-activedescendant", + that.menuItems.eq( item.index ).attr( "id" ) ); + } + } ) + .menu( "instance" ); + + // Don't close the menu on mouseleave + this.menuInstance._off( this.menu, "mouseleave" ); + + // Cancel the menu's collapseAll on document click + this.menuInstance._closeOnDocumentClick = function() { + return false; + }; + + // Selects often contain empty items, but never contain dividers + this.menuInstance._isDivider = function() { + return false; + }; + }, + + refresh: function() { + this._refreshMenu(); + this.buttonItem.replaceWith( + this.buttonItem = this._renderButtonItem( + + // Fall back to an empty object in case there are no options + this._getSelectedItem().data( "ui-selectmenu-item" ) || {} + ) + ); + if ( this.options.width === null ) { + this._resizeButton(); + } + }, + + _refreshMenu: function() { + var item, + options = this.element.find( "option" ); + + this.menu.empty(); + + this._parseOptions( options ); + this._renderMenu( this.menu, this.items ); + + this.menuInstance.refresh(); + this.menuItems = this.menu.find( "li" ) + .not( ".ui-selectmenu-optgroup" ) + .find( ".ui-menu-item-wrapper" ); + + this._rendered = true; + + if ( !options.length ) { + return; + } + + item = this._getSelectedItem(); + + // Update the menu to have the correct item focused + this.menuInstance.focus( null, item ); + this._setAria( item.data( "ui-selectmenu-item" ) ); + + // Set disabled state + this._setOption( "disabled", this.element.prop( "disabled" ) ); + }, + + open: function( event ) { + if ( this.options.disabled ) { + return; + } + + // If this is the first time the menu is being opened, render the items + if ( !this._rendered ) { + this._refreshMenu(); + } else { + + // Menu clears focus on close, reset focus to selected item + this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" ); + this.menuInstance.focus( null, this._getSelectedItem() ); + } + + // If there are no options, don't open the menu + if ( !this.menuItems.length ) { + return; + } + + this.isOpen = true; + this._toggleAttr(); + this._resizeMenu(); + this._position(); + + this._on( this.document, this._documentClick ); + + this._trigger( "open", event ); + }, + + _position: function() { + this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) ); + }, + + close: function( event ) { + if ( !this.isOpen ) { + return; + } + + this.isOpen = false; + this._toggleAttr(); + + this.range = null; + this._off( this.document ); + + this._trigger( "close", event ); + }, + + widget: function() { + return this.button; + }, + + menuWidget: function() { + return this.menu; + }, + + _renderButtonItem: function( item ) { + var buttonItem = $( "<span>" ); + + this._setText( buttonItem, item.label ); + this._addClass( buttonItem, "ui-selectmenu-text" ); + + return buttonItem; + }, + + _renderMenu: function( ul, items ) { + var that = this, + currentOptgroup = ""; + + $.each( items, function( index, item ) { + var li; + + if ( item.optgroup !== currentOptgroup ) { + li = $( "<li>", { + text: item.optgroup + } ); + that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" + + ( item.element.parent( "optgroup" ).prop( "disabled" ) ? + " ui-state-disabled" : + "" ) ); + + li.appendTo( ul ); + + currentOptgroup = item.optgroup; + } + + that._renderItemData( ul, item ); + } ); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-selectmenu-item", item ); + }, + + _renderItem: function( ul, item ) { + var li = $( "<li>" ), + wrapper = $( "<div>", { + title: item.element.attr( "title" ) + } ); + + if ( item.disabled ) { + this._addClass( li, null, "ui-state-disabled" ); + } + this._setText( wrapper, item.label ); + + return li.append( wrapper ).appendTo( ul ); + }, + + _setText: function( element, value ) { + if ( value ) { + element.text( value ); + } else { + element.html( " " ); + } + }, + + _move: function( direction, event ) { + var item, next, + filter = ".ui-menu-item"; + + if ( this.isOpen ) { + item = this.menuItems.eq( this.focusIndex ).parent( "li" ); + } else { + item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); + filter += ":not(.ui-state-disabled)"; + } + + if ( direction === "first" || direction === "last" ) { + next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 ); + } else { + next = item[ direction + "All" ]( filter ).eq( 0 ); + } + + if ( next.length ) { + this.menuInstance.focus( event, next ); + } + }, + + _getSelectedItem: function() { + return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); + }, + + _toggle: function( event ) { + this[ this.isOpen ? "close" : "open" ]( event ); + }, + + _setSelection: function() { + var selection; + + if ( !this.range ) { + return; + } + + if ( window.getSelection ) { + selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange( this.range ); + + // Support: IE8 + } else { + this.range.select(); + } + + // Support: IE + // Setting the text selection kills the button focus in IE, but + // restoring the focus doesn't kill the selection. + this.button.focus(); + }, + + _documentClick: { + mousedown: function( event ) { + if ( !this.isOpen ) { + return; + } + + if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + + $.escapeSelector( this.ids.button ) ).length ) { + this.close( event ); + } + } + }, + + _buttonEvents: { + + // Prevent text selection from being reset when interacting with the selectmenu (#10144) + mousedown: function() { + var selection; + + if ( window.getSelection ) { + selection = window.getSelection(); + if ( selection.rangeCount ) { + this.range = selection.getRangeAt( 0 ); + } + + // Support: IE8 + } else { + this.range = document.selection.createRange(); + } + }, + + click: function( event ) { + this._setSelection(); + this._toggle( event ); + }, + + keydown: function( event ) { + var preventDefault = true; + switch ( event.keyCode ) { + case $.ui.keyCode.TAB: + case $.ui.keyCode.ESCAPE: + this.close( event ); + preventDefault = false; + break; + case $.ui.keyCode.ENTER: + if ( this.isOpen ) { + this._selectFocusedItem( event ); + } + break; + case $.ui.keyCode.UP: + if ( event.altKey ) { + this._toggle( event ); + } else { + this._move( "prev", event ); + } + break; + case $.ui.keyCode.DOWN: + if ( event.altKey ) { + this._toggle( event ); + } else { + this._move( "next", event ); + } + break; + case $.ui.keyCode.SPACE: + if ( this.isOpen ) { + this._selectFocusedItem( event ); + } else { + this._toggle( event ); + } + break; + case $.ui.keyCode.LEFT: + this._move( "prev", event ); + break; + case $.ui.keyCode.RIGHT: + this._move( "next", event ); + break; + case $.ui.keyCode.HOME: + case $.ui.keyCode.PAGE_UP: + this._move( "first", event ); + break; + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_DOWN: + this._move( "last", event ); + break; + default: + this.menu.trigger( event ); + preventDefault = false; + } + + if ( preventDefault ) { + event.preventDefault(); + } + } + }, + + _selectFocusedItem: function( event ) { + var item = this.menuItems.eq( this.focusIndex ).parent( "li" ); + if ( !item.hasClass( "ui-state-disabled" ) ) { + this._select( item.data( "ui-selectmenu-item" ), event ); + } + }, + + _select: function( item, event ) { + var oldIndex = this.element[ 0 ].selectedIndex; + + // Change native select element + this.element[ 0 ].selectedIndex = item.index; + this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) ); + this._setAria( item ); + this._trigger( "select", event, { item: item } ); + + if ( item.index !== oldIndex ) { + this._trigger( "change", event, { item: item } ); + } + + this.close( event ); + }, + + _setAria: function( item ) { + var id = this.menuItems.eq( item.index ).attr( "id" ); + + this.button.attr( { + "aria-labelledby": id, + "aria-activedescendant": id + } ); + this.menu.attr( "aria-activedescendant", id ); + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + var icon = this.button.find( "span.ui-icon" ); + this._removeClass( icon, null, this.options.icons.button ) + ._addClass( icon, null, value.button ); + } + + this._super( key, value ); + + if ( key === "appendTo" ) { + this.menuWrap.appendTo( this._appendTo() ); + } + + if ( key === "width" ) { + this._resizeButton(); + } + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this.menuInstance.option( "disabled", value ); + this.button.attr( "aria-disabled", value ); + this._toggleClass( this.button, null, "ui-state-disabled", value ); + + this.element.prop( "disabled", value ); + if ( value ) { + this.button.attr( "tabindex", -1 ); + this.close(); + } else { + this.button.attr( "tabindex", 0 ); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element || !element[ 0 ] ) { + element = this.element.closest( ".ui-front, dialog" ); + } + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _toggleAttr: function() { + this.button.attr( "aria-expanded", this.isOpen ); + + // We can't use two _toggleClass() calls here, because we need to make sure + // we always remove classes first and add them second, otherwise if both classes have the + // same theme class, it will be removed after we add it. + this._removeClass( this.button, "ui-selectmenu-button-" + + ( this.isOpen ? "closed" : "open" ) ) + ._addClass( this.button, "ui-selectmenu-button-" + + ( this.isOpen ? "open" : "closed" ) ) + ._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen ); + + this.menu.attr( "aria-hidden", !this.isOpen ); + }, + + _resizeButton: function() { + var width = this.options.width; + + // For `width: false`, just remove inline style and stop + if ( width === false ) { + this.button.css( "width", "" ); + return; + } + + // For `width: null`, match the width of the original element + if ( width === null ) { + width = this.element.show().outerWidth(); + this.element.hide(); + } + + this.button.outerWidth( width ); + }, + + _resizeMenu: function() { + this.menu.outerWidth( Math.max( + this.button.outerWidth(), + + // Support: IE10 + // IE10 wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping + this.menu.width( "" ).outerWidth() + 1 + ) ); + }, + + _getCreateOptions: function() { + var options = this._super(); + + options.disabled = this.element.prop( "disabled" ); + + return options; + }, + + _parseOptions: function( options ) { + var that = this, + data = []; + options.each( function( index, item ) { + if ( item.hidden ) { + return; + } + + data.push( that._parseOption( $( item ), index ) ); + } ); + this.items = data; + }, + + _parseOption: function( option, index ) { + var optgroup = option.parent( "optgroup" ); + + return { + element: option, + index: index, + value: option.val(), + label: option.text(), + optgroup: optgroup.attr( "label" ) || "", + disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" ) + }; + }, + + _destroy: function() { + this._unbindFormResetHandler(); + this.menuWrap.remove(); + this.button.remove(); + this.element.show(); + this.element.removeUniqueId(); + this.labels.attr( "for", this.ids.element ); + } + } ] ); + + + /*! + * jQuery UI Slider 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13443,725 +13749,724 @@ var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { //>>css.theme: ../../themes/base/theme.css - -var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, { - version: "1.12.1", - widgetEventPrefix: "slide", - - options: { - animate: false, - classes: { - "ui-slider": "ui-corner-all", - "ui-slider-handle": "ui-corner-all", - - // Note: ui-widget-header isn't the most fittingly semantic framework class for this - // element, but worked best visually with a variety of themes - "ui-slider-range": "ui-corner-all ui-widget-header" - }, - distance: 0, - max: 100, - min: 0, - orientation: "horizontal", - range: false, - step: 1, - value: 0, - values: null, - - // Callbacks - change: null, - slide: null, - start: null, - stop: null - }, - - // Number of pages in a slider - // (how many times can you page up/down to go through the whole range) - numPages: 5, - - _create: function() { - this._keySliding = false; - this._mouseSliding = false; - this._animateOff = true; - this._handleIndex = null; - this._detectOrientation(); - this._mouseInit(); - this._calculateNewMax(); - - this._addClass( "ui-slider ui-slider-" + this.orientation, - "ui-widget ui-widget-content" ); - - this._refresh(); - - this._animateOff = false; - }, - - _refresh: function() { - this._createRange(); - this._createHandles(); - this._setupEvents(); - this._refreshValue(); - }, - - _createHandles: function() { - var i, handleCount, - options = this.options, - existingHandles = this.element.find( ".ui-slider-handle" ), - handle = "<span tabindex='0'></span>", - handles = []; - - handleCount = ( options.values && options.values.length ) || 1; - - if ( existingHandles.length > handleCount ) { - existingHandles.slice( handleCount ).remove(); - existingHandles = existingHandles.slice( 0, handleCount ); - } - - for ( i = existingHandles.length; i < handleCount; i++ ) { - handles.push( handle ); - } - - this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); - - this._addClass( this.handles, "ui-slider-handle", "ui-state-default" ); - - this.handle = this.handles.eq( 0 ); - - this.handles.each( function( i ) { - $( this ) - .data( "ui-slider-handle-index", i ) - .attr( "tabIndex", 0 ); - } ); - }, - - _createRange: function() { - var options = this.options; - - if ( options.range ) { - if ( options.range === true ) { - if ( !options.values ) { - options.values = [ this._valueMin(), this._valueMin() ]; - } else if ( options.values.length && options.values.length !== 2 ) { - options.values = [ options.values[ 0 ], options.values[ 0 ] ]; - } else if ( $.isArray( options.values ) ) { - options.values = options.values.slice( 0 ); - } - } - - if ( !this.range || !this.range.length ) { - this.range = $( "<div>" ) - .appendTo( this.element ); - - this._addClass( this.range, "ui-slider-range" ); - } else { - this._removeClass( this.range, "ui-slider-range-min ui-slider-range-max" ); - - // Handle range switching from true to min/max - this.range.css( { - "left": "", - "bottom": "" - } ); - } - if ( options.range === "min" || options.range === "max" ) { - this._addClass( this.range, "ui-slider-range-" + options.range ); - } - } else { - if ( this.range ) { - this.range.remove(); - } - this.range = null; - } - }, - - _setupEvents: function() { - this._off( this.handles ); - this._on( this.handles, this._handleEvents ); - this._hoverable( this.handles ); - this._focusable( this.handles ); - }, - - _destroy: function() { - this.handles.remove(); - if ( this.range ) { - this.range.remove(); - } - - this._mouseDestroy(); - }, - - _mouseCapture: function( event ) { - var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, - that = this, - o = this.options; - - if ( o.disabled ) { - return false; - } - - this.elementSize = { - width: this.element.outerWidth(), - height: this.element.outerHeight() - }; - this.elementOffset = this.element.offset(); - - position = { x: event.pageX, y: event.pageY }; - normValue = this._normValueFromMouse( position ); - distance = this._valueMax() - this._valueMin() + 1; - this.handles.each( function( i ) { - var thisDistance = Math.abs( normValue - that.values( i ) ); - if ( ( distance > thisDistance ) || - ( distance === thisDistance && - ( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) { - distance = thisDistance; - closestHandle = $( this ); - index = i; - } - } ); - - allowed = this._start( event, index ); - if ( allowed === false ) { - return false; - } - this._mouseSliding = true; - - this._handleIndex = index; - - this._addClass( closestHandle, null, "ui-state-active" ); - closestHandle.trigger( "focus" ); - - offset = closestHandle.offset(); - mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); - this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { - left: event.pageX - offset.left - ( closestHandle.width() / 2 ), - top: event.pageY - offset.top - - ( closestHandle.height() / 2 ) - - ( parseInt( closestHandle.css( "borderTopWidth" ), 10 ) || 0 ) - - ( parseInt( closestHandle.css( "borderBottomWidth" ), 10 ) || 0 ) + - ( parseInt( closestHandle.css( "marginTop" ), 10 ) || 0 ) - }; - - if ( !this.handles.hasClass( "ui-state-hover" ) ) { - this._slide( event, index, normValue ); - } - this._animateOff = true; - return true; - }, - - _mouseStart: function() { - return true; - }, - - _mouseDrag: function( event ) { - var position = { x: event.pageX, y: event.pageY }, - normValue = this._normValueFromMouse( position ); - - this._slide( event, this._handleIndex, normValue ); - - return false; - }, - - _mouseStop: function( event ) { - this._removeClass( this.handles, null, "ui-state-active" ); - this._mouseSliding = false; - - this._stop( event, this._handleIndex ); - this._change( event, this._handleIndex ); - - this._handleIndex = null; - this._clickOffset = null; - this._animateOff = false; - - return false; - }, - - _detectOrientation: function() { - this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; - }, - - _normValueFromMouse: function( position ) { - var pixelTotal, - pixelMouse, - percentMouse, - valueTotal, - valueMouse; - - if ( this.orientation === "horizontal" ) { - pixelTotal = this.elementSize.width; - pixelMouse = position.x - this.elementOffset.left - - ( this._clickOffset ? this._clickOffset.left : 0 ); - } else { - pixelTotal = this.elementSize.height; - pixelMouse = position.y - this.elementOffset.top - - ( this._clickOffset ? this._clickOffset.top : 0 ); - } - - percentMouse = ( pixelMouse / pixelTotal ); - if ( percentMouse > 1 ) { - percentMouse = 1; - } - if ( percentMouse < 0 ) { - percentMouse = 0; - } - if ( this.orientation === "vertical" ) { - percentMouse = 1 - percentMouse; - } - - valueTotal = this._valueMax() - this._valueMin(); - valueMouse = this._valueMin() + percentMouse * valueTotal; - - return this._trimAlignValue( valueMouse ); - }, - - _uiHash: function( index, value, values ) { - var uiHash = { - handle: this.handles[ index ], - handleIndex: index, - value: value !== undefined ? value : this.value() - }; - - if ( this._hasMultipleValues() ) { - uiHash.value = value !== undefined ? value : this.values( index ); - uiHash.values = values || this.values(); - } - - return uiHash; - }, - - _hasMultipleValues: function() { - return this.options.values && this.options.values.length; - }, - - _start: function( event, index ) { - return this._trigger( "start", event, this._uiHash( index ) ); - }, - - _slide: function( event, index, newVal ) { - var allowed, otherVal, - currentValue = this.value(), - newValues = this.values(); - - if ( this._hasMultipleValues() ) { - otherVal = this.values( index ? 0 : 1 ); - currentValue = this.values( index ); - - if ( this.options.values.length === 2 && this.options.range === true ) { - newVal = index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal ); - } - - newValues[ index ] = newVal; - } - - if ( newVal === currentValue ) { - return; - } - - allowed = this._trigger( "slide", event, this._uiHash( index, newVal, newValues ) ); - - // A slide can be canceled by returning false from the slide callback - if ( allowed === false ) { - return; - } - - if ( this._hasMultipleValues() ) { - this.values( index, newVal ); - } else { - this.value( newVal ); - } - }, - - _stop: function( event, index ) { - this._trigger( "stop", event, this._uiHash( index ) ); - }, - - _change: function( event, index ) { - if ( !this._keySliding && !this._mouseSliding ) { - - //store the last changed value index for reference when handles overlap - this._lastChangedValue = index; - this._trigger( "change", event, this._uiHash( index ) ); - } - }, - - value: function( newValue ) { - if ( arguments.length ) { - this.options.value = this._trimAlignValue( newValue ); - this._refreshValue(); - this._change( null, 0 ); - return; - } - - return this._value(); - }, - - values: function( index, newValue ) { - var vals, - newValues, - i; - - if ( arguments.length > 1 ) { - this.options.values[ index ] = this._trimAlignValue( newValue ); - this._refreshValue(); - this._change( null, index ); - return; - } - - if ( arguments.length ) { - if ( $.isArray( arguments[ 0 ] ) ) { - vals = this.options.values; - newValues = arguments[ 0 ]; - for ( i = 0; i < vals.length; i += 1 ) { - vals[ i ] = this._trimAlignValue( newValues[ i ] ); - this._change( null, i ); - } - this._refreshValue(); - } else { - if ( this._hasMultipleValues() ) { - return this._values( index ); - } else { - return this.value(); - } - } - } else { - return this._values(); - } - }, - - _setOption: function( key, value ) { - var i, - valsLength = 0; - - if ( key === "range" && this.options.range === true ) { - if ( value === "min" ) { - this.options.value = this._values( 0 ); - this.options.values = null; - } else if ( value === "max" ) { - this.options.value = this._values( this.options.values.length - 1 ); - this.options.values = null; - } - } - - if ( $.isArray( this.options.values ) ) { - valsLength = this.options.values.length; - } - - this._super( key, value ); - - switch ( key ) { - case "orientation": - this._detectOrientation(); - this._removeClass( "ui-slider-horizontal ui-slider-vertical" ) - ._addClass( "ui-slider-" + this.orientation ); - this._refreshValue(); - if ( this.options.range ) { - this._refreshRange( value ); - } - - // Reset positioning from previous orientation - this.handles.css( value === "horizontal" ? "bottom" : "left", "" ); - break; - case "value": - this._animateOff = true; - this._refreshValue(); - this._change( null, 0 ); - this._animateOff = false; - break; - case "values": - this._animateOff = true; - this._refreshValue(); - - // Start from the last handle to prevent unreachable handles (#9046) - for ( i = valsLength - 1; i >= 0; i-- ) { - this._change( null, i ); - } - this._animateOff = false; - break; - case "step": - case "min": - case "max": - this._animateOff = true; - this._calculateNewMax(); - this._refreshValue(); - this._animateOff = false; - break; - case "range": - this._animateOff = true; - this._refresh(); - this._animateOff = false; - break; - } - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this._toggleClass( null, "ui-state-disabled", !!value ); - }, - - //internal value getter - // _value() returns value trimmed by min and max, aligned by step - _value: function() { - var val = this.options.value; - val = this._trimAlignValue( val ); - - return val; - }, - - //internal values getter - // _values() returns array of values trimmed by min and max, aligned by step - // _values( index ) returns single value trimmed by min and max, aligned by step - _values: function( index ) { - var val, - vals, - i; - - if ( arguments.length ) { - val = this.options.values[ index ]; - val = this._trimAlignValue( val ); - - return val; - } else if ( this._hasMultipleValues() ) { - - // .slice() creates a copy of the array - // this copy gets trimmed by min and max and then returned - vals = this.options.values.slice(); - for ( i = 0; i < vals.length; i += 1 ) { - vals[ i ] = this._trimAlignValue( vals[ i ] ); - } - - return vals; - } else { - return []; - } - }, - - // Returns the step-aligned value that val is closest to, between (inclusive) min and max - _trimAlignValue: function( val ) { - if ( val <= this._valueMin() ) { - return this._valueMin(); - } - if ( val >= this._valueMax() ) { - return this._valueMax(); - } - var step = ( this.options.step > 0 ) ? this.options.step : 1, - valModStep = ( val - this._valueMin() ) % step, - alignValue = val - valModStep; - - if ( Math.abs( valModStep ) * 2 >= step ) { - alignValue += ( valModStep > 0 ) ? step : ( -step ); - } - - // Since JavaScript has problems with large floats, round - // the final value to 5 digits after the decimal point (see #4124) - return parseFloat( alignValue.toFixed( 5 ) ); - }, - - _calculateNewMax: function() { - var max = this.options.max, - min = this._valueMin(), - step = this.options.step, - aboveMin = Math.round( ( max - min ) / step ) * step; - max = aboveMin + min; - if ( max > this.options.max ) { - - //If max is not divisible by step, rounding off may increase its value - max -= step; - } - this.max = parseFloat( max.toFixed( this._precision() ) ); - }, - - _precision: function() { - var precision = this._precisionOf( this.options.step ); - if ( this.options.min !== null ) { - precision = Math.max( precision, this._precisionOf( this.options.min ) ); - } - return precision; - }, - - _precisionOf: function( num ) { - var str = num.toString(), - decimal = str.indexOf( "." ); - return decimal === -1 ? 0 : str.length - decimal - 1; - }, - - _valueMin: function() { - return this.options.min; - }, - - _valueMax: function() { - return this.max; - }, - - _refreshRange: function( orientation ) { - if ( orientation === "vertical" ) { - this.range.css( { "width": "", "left": "" } ); - } - if ( orientation === "horizontal" ) { - this.range.css( { "height": "", "bottom": "" } ); - } - }, - - _refreshValue: function() { - var lastValPercent, valPercent, value, valueMin, valueMax, - oRange = this.options.range, - o = this.options, - that = this, - animate = ( !this._animateOff ) ? o.animate : false, - _set = {}; - - if ( this._hasMultipleValues() ) { - this.handles.each( function( i ) { - valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() - - that._valueMin() ) * 100; - _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; - $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); - if ( that.options.range === true ) { - if ( that.orientation === "horizontal" ) { - if ( i === 0 ) { - that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - left: valPercent + "%" - }, o.animate ); - } - if ( i === 1 ) { - that.range[ animate ? "animate" : "css" ]( { - width: ( valPercent - lastValPercent ) + "%" - }, { - queue: false, - duration: o.animate - } ); - } - } else { - if ( i === 0 ) { - that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - bottom: ( valPercent ) + "%" - }, o.animate ); - } - if ( i === 1 ) { - that.range[ animate ? "animate" : "css" ]( { - height: ( valPercent - lastValPercent ) + "%" - }, { - queue: false, - duration: o.animate - } ); - } - } - } - lastValPercent = valPercent; - } ); - } else { - value = this.value(); - valueMin = this._valueMin(); - valueMax = this._valueMax(); - valPercent = ( valueMax !== valueMin ) ? - ( value - valueMin ) / ( valueMax - valueMin ) * 100 : - 0; - _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; - this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); - - if ( oRange === "min" && this.orientation === "horizontal" ) { - this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - width: valPercent + "%" - }, o.animate ); - } - if ( oRange === "max" && this.orientation === "horizontal" ) { - this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - width: ( 100 - valPercent ) + "%" - }, o.animate ); - } - if ( oRange === "min" && this.orientation === "vertical" ) { - this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - height: valPercent + "%" - }, o.animate ); - } - if ( oRange === "max" && this.orientation === "vertical" ) { - this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { - height: ( 100 - valPercent ) + "%" - }, o.animate ); - } - } - }, - - _handleEvents: { - keydown: function( event ) { - var allowed, curVal, newVal, step, - index = $( event.target ).data( "ui-slider-handle-index" ); - - switch ( event.keyCode ) { - case $.ui.keyCode.HOME: - case $.ui.keyCode.END: - case $.ui.keyCode.PAGE_UP: - case $.ui.keyCode.PAGE_DOWN: - case $.ui.keyCode.UP: - case $.ui.keyCode.RIGHT: - case $.ui.keyCode.DOWN: - case $.ui.keyCode.LEFT: - event.preventDefault(); - if ( !this._keySliding ) { - this._keySliding = true; - this._addClass( $( event.target ), null, "ui-state-active" ); - allowed = this._start( event, index ); - if ( allowed === false ) { - return; - } - } - break; - } - - step = this.options.step; - if ( this._hasMultipleValues() ) { - curVal = newVal = this.values( index ); - } else { - curVal = newVal = this.value(); - } - - switch ( event.keyCode ) { - case $.ui.keyCode.HOME: - newVal = this._valueMin(); - break; - case $.ui.keyCode.END: - newVal = this._valueMax(); - break; - case $.ui.keyCode.PAGE_UP: - newVal = this._trimAlignValue( - curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages ) - ); - break; - case $.ui.keyCode.PAGE_DOWN: - newVal = this._trimAlignValue( - curVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) ); - break; - case $.ui.keyCode.UP: - case $.ui.keyCode.RIGHT: - if ( curVal === this._valueMax() ) { - return; - } - newVal = this._trimAlignValue( curVal + step ); - break; - case $.ui.keyCode.DOWN: - case $.ui.keyCode.LEFT: - if ( curVal === this._valueMin() ) { - return; - } - newVal = this._trimAlignValue( curVal - step ); - break; - } - - this._slide( event, index, newVal ); - }, - keyup: function( event ) { - var index = $( event.target ).data( "ui-slider-handle-index" ); - - if ( this._keySliding ) { - this._keySliding = false; - this._stop( event, index ); - this._change( event, index ); - this._removeClass( $( event.target ), null, "ui-state-active" ); - } - } - } -} ); - - -/*! - * jQuery UI Spinner 1.12.1 + var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, { + version: "1.13.0", + widgetEventPrefix: "slide", + + options: { + animate: false, + classes: { + "ui-slider": "ui-corner-all", + "ui-slider-handle": "ui-corner-all", + + // Note: ui-widget-header isn't the most fittingly semantic framework class for this + // element, but worked best visually with a variety of themes + "ui-slider-range": "ui-corner-all ui-widget-header" + }, + distance: 0, + max: 100, + min: 0, + orientation: "horizontal", + range: false, + step: 1, + value: 0, + values: null, + + // Callbacks + change: null, + slide: null, + start: null, + stop: null + }, + + // Number of pages in a slider + // (how many times can you page up/down to go through the whole range) + numPages: 5, + + _create: function() { + this._keySliding = false; + this._mouseSliding = false; + this._animateOff = true; + this._handleIndex = null; + this._detectOrientation(); + this._mouseInit(); + this._calculateNewMax(); + + this._addClass( "ui-slider ui-slider-" + this.orientation, + "ui-widget ui-widget-content" ); + + this._refresh(); + + this._animateOff = false; + }, + + _refresh: function() { + this._createRange(); + this._createHandles(); + this._setupEvents(); + this._refreshValue(); + }, + + _createHandles: function() { + var i, handleCount, + options = this.options, + existingHandles = this.element.find( ".ui-slider-handle" ), + handle = "<span tabindex='0'></span>", + handles = []; + + handleCount = ( options.values && options.values.length ) || 1; + + if ( existingHandles.length > handleCount ) { + existingHandles.slice( handleCount ).remove(); + existingHandles = existingHandles.slice( 0, handleCount ); + } + + for ( i = existingHandles.length; i < handleCount; i++ ) { + handles.push( handle ); + } + + this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); + + this._addClass( this.handles, "ui-slider-handle", "ui-state-default" ); + + this.handle = this.handles.eq( 0 ); + + this.handles.each( function( i ) { + $( this ) + .data( "ui-slider-handle-index", i ) + .attr( "tabIndex", 0 ); + } ); + }, + + _createRange: function() { + var options = this.options; + + if ( options.range ) { + if ( options.range === true ) { + if ( !options.values ) { + options.values = [ this._valueMin(), this._valueMin() ]; + } else if ( options.values.length && options.values.length !== 2 ) { + options.values = [ options.values[ 0 ], options.values[ 0 ] ]; + } else if ( Array.isArray( options.values ) ) { + options.values = options.values.slice( 0 ); + } + } + + if ( !this.range || !this.range.length ) { + this.range = $( "<div>" ) + .appendTo( this.element ); + + this._addClass( this.range, "ui-slider-range" ); + } else { + this._removeClass( this.range, "ui-slider-range-min ui-slider-range-max" ); + + // Handle range switching from true to min/max + this.range.css( { + "left": "", + "bottom": "" + } ); + } + if ( options.range === "min" || options.range === "max" ) { + this._addClass( this.range, "ui-slider-range-" + options.range ); + } + } else { + if ( this.range ) { + this.range.remove(); + } + this.range = null; + } + }, + + _setupEvents: function() { + this._off( this.handles ); + this._on( this.handles, this._handleEvents ); + this._hoverable( this.handles ); + this._focusable( this.handles ); + }, + + _destroy: function() { + this.handles.remove(); + if ( this.range ) { + this.range.remove(); + } + + this._mouseDestroy(); + }, + + _mouseCapture: function( event ) { + var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, + that = this, + o = this.options; + + if ( o.disabled ) { + return false; + } + + this.elementSize = { + width: this.element.outerWidth(), + height: this.element.outerHeight() + }; + this.elementOffset = this.element.offset(); + + position = { x: event.pageX, y: event.pageY }; + normValue = this._normValueFromMouse( position ); + distance = this._valueMax() - this._valueMin() + 1; + this.handles.each( function( i ) { + var thisDistance = Math.abs( normValue - that.values( i ) ); + if ( ( distance > thisDistance ) || + ( distance === thisDistance && + ( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) { + distance = thisDistance; + closestHandle = $( this ); + index = i; + } + } ); + + allowed = this._start( event, index ); + if ( allowed === false ) { + return false; + } + this._mouseSliding = true; + + this._handleIndex = index; + + this._addClass( closestHandle, null, "ui-state-active" ); + closestHandle.trigger( "focus" ); + + offset = closestHandle.offset(); + mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); + this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { + left: event.pageX - offset.left - ( closestHandle.width() / 2 ), + top: event.pageY - offset.top - + ( closestHandle.height() / 2 ) - + ( parseInt( closestHandle.css( "borderTopWidth" ), 10 ) || 0 ) - + ( parseInt( closestHandle.css( "borderBottomWidth" ), 10 ) || 0 ) + + ( parseInt( closestHandle.css( "marginTop" ), 10 ) || 0 ) + }; + + if ( !this.handles.hasClass( "ui-state-hover" ) ) { + this._slide( event, index, normValue ); + } + this._animateOff = true; + return true; + }, + + _mouseStart: function() { + return true; + }, + + _mouseDrag: function( event ) { + var position = { x: event.pageX, y: event.pageY }, + normValue = this._normValueFromMouse( position ); + + this._slide( event, this._handleIndex, normValue ); + + return false; + }, + + _mouseStop: function( event ) { + this._removeClass( this.handles, null, "ui-state-active" ); + this._mouseSliding = false; + + this._stop( event, this._handleIndex ); + this._change( event, this._handleIndex ); + + this._handleIndex = null; + this._clickOffset = null; + this._animateOff = false; + + return false; + }, + + _detectOrientation: function() { + this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; + }, + + _normValueFromMouse: function( position ) { + var pixelTotal, + pixelMouse, + percentMouse, + valueTotal, + valueMouse; + + if ( this.orientation === "horizontal" ) { + pixelTotal = this.elementSize.width; + pixelMouse = position.x - this.elementOffset.left - + ( this._clickOffset ? this._clickOffset.left : 0 ); + } else { + pixelTotal = this.elementSize.height; + pixelMouse = position.y - this.elementOffset.top - + ( this._clickOffset ? this._clickOffset.top : 0 ); + } + + percentMouse = ( pixelMouse / pixelTotal ); + if ( percentMouse > 1 ) { + percentMouse = 1; + } + if ( percentMouse < 0 ) { + percentMouse = 0; + } + if ( this.orientation === "vertical" ) { + percentMouse = 1 - percentMouse; + } + + valueTotal = this._valueMax() - this._valueMin(); + valueMouse = this._valueMin() + percentMouse * valueTotal; + + return this._trimAlignValue( valueMouse ); + }, + + _uiHash: function( index, value, values ) { + var uiHash = { + handle: this.handles[ index ], + handleIndex: index, + value: value !== undefined ? value : this.value() + }; + + if ( this._hasMultipleValues() ) { + uiHash.value = value !== undefined ? value : this.values( index ); + uiHash.values = values || this.values(); + } + + return uiHash; + }, + + _hasMultipleValues: function() { + return this.options.values && this.options.values.length; + }, + + _start: function( event, index ) { + return this._trigger( "start", event, this._uiHash( index ) ); + }, + + _slide: function( event, index, newVal ) { + var allowed, otherVal, + currentValue = this.value(), + newValues = this.values(); + + if ( this._hasMultipleValues() ) { + otherVal = this.values( index ? 0 : 1 ); + currentValue = this.values( index ); + + if ( this.options.values.length === 2 && this.options.range === true ) { + newVal = index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal ); + } + + newValues[ index ] = newVal; + } + + if ( newVal === currentValue ) { + return; + } + + allowed = this._trigger( "slide", event, this._uiHash( index, newVal, newValues ) ); + + // A slide can be canceled by returning false from the slide callback + if ( allowed === false ) { + return; + } + + if ( this._hasMultipleValues() ) { + this.values( index, newVal ); + } else { + this.value( newVal ); + } + }, + + _stop: function( event, index ) { + this._trigger( "stop", event, this._uiHash( index ) ); + }, + + _change: function( event, index ) { + if ( !this._keySliding && !this._mouseSliding ) { + + //store the last changed value index for reference when handles overlap + this._lastChangedValue = index; + this._trigger( "change", event, this._uiHash( index ) ); + } + }, + + value: function( newValue ) { + if ( arguments.length ) { + this.options.value = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, 0 ); + return; + } + + return this._value(); + }, + + values: function( index, newValue ) { + var vals, + newValues, + i; + + if ( arguments.length > 1 ) { + this.options.values[ index ] = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, index ); + return; + } + + if ( arguments.length ) { + if ( Array.isArray( arguments[ 0 ] ) ) { + vals = this.options.values; + newValues = arguments[ 0 ]; + for ( i = 0; i < vals.length; i += 1 ) { + vals[ i ] = this._trimAlignValue( newValues[ i ] ); + this._change( null, i ); + } + this._refreshValue(); + } else { + if ( this._hasMultipleValues() ) { + return this._values( index ); + } else { + return this.value(); + } + } + } else { + return this._values(); + } + }, + + _setOption: function( key, value ) { + var i, + valsLength = 0; + + if ( key === "range" && this.options.range === true ) { + if ( value === "min" ) { + this.options.value = this._values( 0 ); + this.options.values = null; + } else if ( value === "max" ) { + this.options.value = this._values( this.options.values.length - 1 ); + this.options.values = null; + } + } + + if ( Array.isArray( this.options.values ) ) { + valsLength = this.options.values.length; + } + + this._super( key, value ); + + switch ( key ) { + case "orientation": + this._detectOrientation(); + this._removeClass( "ui-slider-horizontal ui-slider-vertical" ) + ._addClass( "ui-slider-" + this.orientation ); + this._refreshValue(); + if ( this.options.range ) { + this._refreshRange( value ); + } + + // Reset positioning from previous orientation + this.handles.css( value === "horizontal" ? "bottom" : "left", "" ); + break; + case "value": + this._animateOff = true; + this._refreshValue(); + this._change( null, 0 ); + this._animateOff = false; + break; + case "values": + this._animateOff = true; + this._refreshValue(); + + // Start from the last handle to prevent unreachable handles (#9046) + for ( i = valsLength - 1; i >= 0; i-- ) { + this._change( null, i ); + } + this._animateOff = false; + break; + case "step": + case "min": + case "max": + this._animateOff = true; + this._calculateNewMax(); + this._refreshValue(); + this._animateOff = false; + break; + case "range": + this._animateOff = true; + this._refresh(); + this._animateOff = false; + break; + } + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this._toggleClass( null, "ui-state-disabled", !!value ); + }, + + //internal value getter + // _value() returns value trimmed by min and max, aligned by step + _value: function() { + var val = this.options.value; + val = this._trimAlignValue( val ); + + return val; + }, + + //internal values getter + // _values() returns array of values trimmed by min and max, aligned by step + // _values( index ) returns single value trimmed by min and max, aligned by step + _values: function( index ) { + var val, + vals, + i; + + if ( arguments.length ) { + val = this.options.values[ index ]; + val = this._trimAlignValue( val ); + + return val; + } else if ( this._hasMultipleValues() ) { + + // .slice() creates a copy of the array + // this copy gets trimmed by min and max and then returned + vals = this.options.values.slice(); + for ( i = 0; i < vals.length; i += 1 ) { + vals[ i ] = this._trimAlignValue( vals[ i ] ); + } + + return vals; + } else { + return []; + } + }, + + // Returns the step-aligned value that val is closest to, between (inclusive) min and max + _trimAlignValue: function( val ) { + if ( val <= this._valueMin() ) { + return this._valueMin(); + } + if ( val >= this._valueMax() ) { + return this._valueMax(); + } + var step = ( this.options.step > 0 ) ? this.options.step : 1, + valModStep = ( val - this._valueMin() ) % step, + alignValue = val - valModStep; + + if ( Math.abs( valModStep ) * 2 >= step ) { + alignValue += ( valModStep > 0 ) ? step : ( -step ); + } + + // Since JavaScript has problems with large floats, round + // the final value to 5 digits after the decimal point (see #4124) + return parseFloat( alignValue.toFixed( 5 ) ); + }, + + _calculateNewMax: function() { + var max = this.options.max, + min = this._valueMin(), + step = this.options.step, + aboveMin = Math.round( ( max - min ) / step ) * step; + max = aboveMin + min; + if ( max > this.options.max ) { + + //If max is not divisible by step, rounding off may increase its value + max -= step; + } + this.max = parseFloat( max.toFixed( this._precision() ) ); + }, + + _precision: function() { + var precision = this._precisionOf( this.options.step ); + if ( this.options.min !== null ) { + precision = Math.max( precision, this._precisionOf( this.options.min ) ); + } + return precision; + }, + + _precisionOf: function( num ) { + var str = num.toString(), + decimal = str.indexOf( "." ); + return decimal === -1 ? 0 : str.length - decimal - 1; + }, + + _valueMin: function() { + return this.options.min; + }, + + _valueMax: function() { + return this.max; + }, + + _refreshRange: function( orientation ) { + if ( orientation === "vertical" ) { + this.range.css( { "width": "", "left": "" } ); + } + if ( orientation === "horizontal" ) { + this.range.css( { "height": "", "bottom": "" } ); + } + }, + + _refreshValue: function() { + var lastValPercent, valPercent, value, valueMin, valueMax, + oRange = this.options.range, + o = this.options, + that = this, + animate = ( !this._animateOff ) ? o.animate : false, + _set = {}; + + if ( this._hasMultipleValues() ) { + this.handles.each( function( i ) { + valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() - + that._valueMin() ) * 100; + _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + if ( that.options.range === true ) { + if ( that.orientation === "horizontal" ) { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + left: valPercent + "%" + }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { + width: ( valPercent - lastValPercent ) + "%" + }, { + queue: false, + duration: o.animate + } ); + } + } else { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + bottom: ( valPercent ) + "%" + }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { + height: ( valPercent - lastValPercent ) + "%" + }, { + queue: false, + duration: o.animate + } ); + } + } + } + lastValPercent = valPercent; + } ); + } else { + value = this.value(); + valueMin = this._valueMin(); + valueMax = this._valueMax(); + valPercent = ( valueMax !== valueMin ) ? + ( value - valueMin ) / ( valueMax - valueMin ) * 100 : + 0; + _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + + if ( oRange === "min" && this.orientation === "horizontal" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + width: valPercent + "%" + }, o.animate ); + } + if ( oRange === "max" && this.orientation === "horizontal" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + width: ( 100 - valPercent ) + "%" + }, o.animate ); + } + if ( oRange === "min" && this.orientation === "vertical" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + height: valPercent + "%" + }, o.animate ); + } + if ( oRange === "max" && this.orientation === "vertical" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { + height: ( 100 - valPercent ) + "%" + }, o.animate ); + } + } + }, + + _handleEvents: { + keydown: function( event ) { + var allowed, curVal, newVal, step, + index = $( event.target ).data( "ui-slider-handle-index" ); + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_UP: + case $.ui.keyCode.PAGE_DOWN: + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + event.preventDefault(); + if ( !this._keySliding ) { + this._keySliding = true; + this._addClass( $( event.target ), null, "ui-state-active" ); + allowed = this._start( event, index ); + if ( allowed === false ) { + return; + } + } + break; + } + + step = this.options.step; + if ( this._hasMultipleValues() ) { + curVal = newVal = this.values( index ); + } else { + curVal = newVal = this.value(); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + newVal = this._valueMin(); + break; + case $.ui.keyCode.END: + newVal = this._valueMax(); + break; + case $.ui.keyCode.PAGE_UP: + newVal = this._trimAlignValue( + curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages ) + ); + break; + case $.ui.keyCode.PAGE_DOWN: + newVal = this._trimAlignValue( + curVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) ); + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + if ( curVal === this._valueMax() ) { + return; + } + newVal = this._trimAlignValue( curVal + step ); + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + if ( curVal === this._valueMin() ) { + return; + } + newVal = this._trimAlignValue( curVal - step ); + break; + } + + this._slide( event, index, newVal ); + }, + keyup: function( event ) { + var index = $( event.target ).data( "ui-slider-handle-index" ); + + if ( this._keySliding ) { + this._keySliding = false; + this._stop( event, index ); + this._change( event, index ); + this._removeClass( $( event.target ), null, "ui-state-active" ); + } + } + } + } ); + + + /*! + * jQuery UI Spinner 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14179,547 +14484,550 @@ var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, { //>>css.theme: ../../themes/base/theme.css - -function spinnerModifer( fn ) { - return function() { - var previous = this.element.val(); - fn.apply( this, arguments ); - this._refresh(); - if ( previous !== this.element.val() ) { - this._trigger( "change" ); - } - }; -} - -$.widget( "ui.spinner", { - version: "1.12.1", - defaultElement: "<input>", - widgetEventPrefix: "spin", - options: { - classes: { - "ui-spinner": "ui-corner-all", - "ui-spinner-down": "ui-corner-br", - "ui-spinner-up": "ui-corner-tr" - }, - culture: null, - icons: { - down: "ui-icon-triangle-1-s", - up: "ui-icon-triangle-1-n" - }, - incremental: true, - max: null, - min: null, - numberFormat: null, - page: 10, - step: 1, - - change: null, - spin: null, - start: null, - stop: null - }, - - _create: function() { - - // handle string values that need to be parsed - this._setOption( "max", this.options.max ); - this._setOption( "min", this.options.min ); - this._setOption( "step", this.options.step ); - - // Only format if there is a value, prevents the field from being marked - // as invalid in Firefox, see #9573. - if ( this.value() !== "" ) { - - // Format the value, but don't constrain. - this._value( this.element.val(), true ); - } - - this._draw(); - this._on( this._events ); - this._refresh(); - - // Turning off autocomplete prevents the browser from remembering the - // value when navigating through history, so we re-enable autocomplete - // if the page is unloaded before the widget is destroyed. #7790 - this._on( this.window, { - beforeunload: function() { - this.element.removeAttr( "autocomplete" ); - } - } ); - }, - - _getCreateOptions: function() { - var options = this._super(); - var element = this.element; - - $.each( [ "min", "max", "step" ], function( i, option ) { - var value = element.attr( option ); - if ( value != null && value.length ) { - options[ option ] = value; - } - } ); - - return options; - }, - - _events: { - keydown: function( event ) { - if ( this._start( event ) && this._keydown( event ) ) { - event.preventDefault(); - } - }, - keyup: "_stop", - focus: function() { - this.previous = this.element.val(); - }, - blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; - } - - this._stop(); - this._refresh(); - if ( this.previous !== this.element.val() ) { - this._trigger( "change", event ); - } - }, - mousewheel: function( event, delta ) { - if ( !delta ) { - return; - } - if ( !this.spinning && !this._start( event ) ) { - return false; - } - - this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event ); - clearTimeout( this.mousewheelTimer ); - this.mousewheelTimer = this._delay( function() { - if ( this.spinning ) { - this._stop( event ); - } - }, 100 ); - event.preventDefault(); - }, - "mousedown .ui-spinner-button": function( event ) { - var previous; - - // We never want the buttons to have focus; whenever the user is - // interacting with the spinner, the focus should be on the input. - // If the input is focused then this.previous is properly set from - // when the input first received focus. If the input is not focused - // then we need to set this.previous based on the value before spinning. - previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ? - this.previous : this.element.val(); - function checkFocus() { - var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ); - if ( !isActive ) { - this.element.trigger( "focus" ); - this.previous = previous; - - // support: IE - // IE sets focus asynchronously, so we need to check if focus - // moved off of the input because the user clicked on the button. - this._delay( function() { - this.previous = previous; - } ); - } - } - - // Ensure focus is on (or stays on) the text field - event.preventDefault(); - checkFocus.call( this ); - - // Support: IE - // IE doesn't prevent moving focus even with event.preventDefault() - // so we set a flag to know when we should ignore the blur event - // and check (again) if focus moved off of the input. - this.cancelBlur = true; - this._delay( function() { - delete this.cancelBlur; - checkFocus.call( this ); - } ); - - if ( this._start( event ) === false ) { - return; - } - - this._repeat( null, $( event.currentTarget ) - .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); - }, - "mouseup .ui-spinner-button": "_stop", - "mouseenter .ui-spinner-button": function( event ) { - - // button will add ui-state-active if mouse was down while mouseleave and kept down - if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { - return; - } - - if ( this._start( event ) === false ) { - return false; - } - this._repeat( null, $( event.currentTarget ) - .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); - }, - - // TODO: do we really want to consider this a stop? - // shouldn't we just stop the repeater and wait until mouseup before - // we trigger the stop event? - "mouseleave .ui-spinner-button": "_stop" - }, - - // Support mobile enhanced option and make backcompat more sane - _enhance: function() { - this.uiSpinner = this.element - .attr( "autocomplete", "off" ) - .wrap( "<span>" ) - .parent() - - // Add buttons - .append( - "<a></a><a></a>" - ); - }, - - _draw: function() { - this._enhance(); - - this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" ); - this._addClass( "ui-spinner-input" ); - - this.element.attr( "role", "spinbutton" ); - - // Button bindings - this.buttons = this.uiSpinner.children( "a" ) - .attr( "tabIndex", -1 ) - .attr( "aria-hidden", true ) - .button( { - classes: { - "ui-button": "" - } - } ); - - // TODO: Right now button does not support classes this is already updated in button PR - this._removeClass( this.buttons, "ui-corner-all" ); - - this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" ); - this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" ); - this.buttons.first().button( { - "icon": this.options.icons.up, - "showLabel": false - } ); - this.buttons.last().button( { - "icon": this.options.icons.down, - "showLabel": false - } ); - - // IE 6 doesn't understand height: 50% for the buttons - // unless the wrapper has an explicit height - if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) && - this.uiSpinner.height() > 0 ) { - this.uiSpinner.height( this.uiSpinner.height() ); - } - }, - - _keydown: function( event ) { - var options = this.options, - keyCode = $.ui.keyCode; - - switch ( event.keyCode ) { - case keyCode.UP: - this._repeat( null, 1, event ); - return true; - case keyCode.DOWN: - this._repeat( null, -1, event ); - return true; - case keyCode.PAGE_UP: - this._repeat( null, options.page, event ); - return true; - case keyCode.PAGE_DOWN: - this._repeat( null, -options.page, event ); - return true; - } - - return false; - }, - - _start: function( event ) { - if ( !this.spinning && this._trigger( "start", event ) === false ) { - return false; - } - - if ( !this.counter ) { - this.counter = 1; - } - this.spinning = true; - return true; - }, - - _repeat: function( i, steps, event ) { - i = i || 500; - - clearTimeout( this.timer ); - this.timer = this._delay( function() { - this._repeat( 40, steps, event ); - }, i ); - - this._spin( steps * this.options.step, event ); - }, - - _spin: function( step, event ) { - var value = this.value() || 0; - - if ( !this.counter ) { - this.counter = 1; - } - - value = this._adjustValue( value + step * this._increment( this.counter ) ); - - if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) { - this._value( value ); - this.counter++; - } - }, - - _increment: function( i ) { - var incremental = this.options.incremental; - - if ( incremental ) { - return $.isFunction( incremental ) ? - incremental( i ) : - Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 ); - } - - return 1; - }, - - _precision: function() { - var precision = this._precisionOf( this.options.step ); - if ( this.options.min !== null ) { - precision = Math.max( precision, this._precisionOf( this.options.min ) ); - } - return precision; - }, - - _precisionOf: function( num ) { - var str = num.toString(), - decimal = str.indexOf( "." ); - return decimal === -1 ? 0 : str.length - decimal - 1; - }, - - _adjustValue: function( value ) { - var base, aboveMin, - options = this.options; - - // Make sure we're at a valid step - // - find out where we are relative to the base (min or 0) - base = options.min !== null ? options.min : 0; - aboveMin = value - base; - - // - round to the nearest step - aboveMin = Math.round( aboveMin / options.step ) * options.step; - - // - rounding is based on 0, so adjust back to our base - value = base + aboveMin; - - // Fix precision from bad JS floating point math - value = parseFloat( value.toFixed( this._precision() ) ); - - // Clamp the value - if ( options.max !== null && value > options.max ) { - return options.max; - } - if ( options.min !== null && value < options.min ) { - return options.min; - } - - return value; - }, - - _stop: function( event ) { - if ( !this.spinning ) { - return; - } - - clearTimeout( this.timer ); - clearTimeout( this.mousewheelTimer ); - this.counter = 0; - this.spinning = false; - this._trigger( "stop", event ); - }, - - _setOption: function( key, value ) { - var prevValue, first, last; - - if ( key === "culture" || key === "numberFormat" ) { - prevValue = this._parse( this.element.val() ); - this.options[ key ] = value; - this.element.val( this._format( prevValue ) ); - return; - } - - if ( key === "max" || key === "min" || key === "step" ) { - if ( typeof value === "string" ) { - value = this._parse( value ); - } - } - if ( key === "icons" ) { - first = this.buttons.first().find( ".ui-icon" ); - this._removeClass( first, null, this.options.icons.up ); - this._addClass( first, null, value.up ); - last = this.buttons.last().find( ".ui-icon" ); - this._removeClass( last, null, this.options.icons.down ); - this._addClass( last, null, value.down ); - } - - this._super( key, value ); - }, - - _setOptionDisabled: function( value ) { - this._super( value ); - - this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value ); - this.element.prop( "disabled", !!value ); - this.buttons.button( value ? "disable" : "enable" ); - }, - - _setOptions: spinnerModifer( function( options ) { - this._super( options ); - } ), - - _parse: function( val ) { - if ( typeof val === "string" && val !== "" ) { - val = window.Globalize && this.options.numberFormat ? - Globalize.parseFloat( val, 10, this.options.culture ) : +val; - } - return val === "" || isNaN( val ) ? null : val; - }, - - _format: function( value ) { - if ( value === "" ) { - return ""; - } - return window.Globalize && this.options.numberFormat ? - Globalize.format( value, this.options.numberFormat, this.options.culture ) : - value; - }, - - _refresh: function() { - this.element.attr( { - "aria-valuemin": this.options.min, - "aria-valuemax": this.options.max, - - // TODO: what should we do with values that can't be parsed? - "aria-valuenow": this._parse( this.element.val() ) - } ); - }, - - isValid: function() { - var value = this.value(); - - // Null is invalid - if ( value === null ) { - return false; - } - - // If value gets adjusted, it's invalid - return value === this._adjustValue( value ); - }, - - // Update the value without triggering change - _value: function( value, allowAny ) { - var parsed; - if ( value !== "" ) { - parsed = this._parse( value ); - if ( parsed !== null ) { - if ( !allowAny ) { - parsed = this._adjustValue( parsed ); - } - value = this._format( parsed ); - } - } - this.element.val( value ); - this._refresh(); - }, - - _destroy: function() { - this.element - .prop( "disabled", false ) - .removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" ); - - this.uiSpinner.replaceWith( this.element ); - }, - - stepUp: spinnerModifer( function( steps ) { - this._stepUp( steps ); - } ), - _stepUp: function( steps ) { - if ( this._start() ) { - this._spin( ( steps || 1 ) * this.options.step ); - this._stop(); - } - }, - - stepDown: spinnerModifer( function( steps ) { - this._stepDown( steps ); - } ), - _stepDown: function( steps ) { - if ( this._start() ) { - this._spin( ( steps || 1 ) * -this.options.step ); - this._stop(); - } - }, - - pageUp: spinnerModifer( function( pages ) { - this._stepUp( ( pages || 1 ) * this.options.page ); - } ), - - pageDown: spinnerModifer( function( pages ) { - this._stepDown( ( pages || 1 ) * this.options.page ); - } ), - - value: function( newVal ) { - if ( !arguments.length ) { - return this._parse( this.element.val() ); - } - spinnerModifer( this._value ).call( this, newVal ); - }, - - widget: function() { - return this.uiSpinner; - } -} ); + function spinnerModifier( fn ) { + return function() { + var previous = this.element.val(); + fn.apply( this, arguments ); + this._refresh(); + if ( previous !== this.element.val() ) { + this._trigger( "change" ); + } + }; + } + + $.widget( "ui.spinner", { + version: "1.13.0", + defaultElement: "<input>", + widgetEventPrefix: "spin", + options: { + classes: { + "ui-spinner": "ui-corner-all", + "ui-spinner-down": "ui-corner-br", + "ui-spinner-up": "ui-corner-tr" + }, + culture: null, + icons: { + down: "ui-icon-triangle-1-s", + up: "ui-icon-triangle-1-n" + }, + incremental: true, + max: null, + min: null, + numberFormat: null, + page: 10, + step: 1, + + change: null, + spin: null, + start: null, + stop: null + }, + + _create: function() { + + // handle string values that need to be parsed + this._setOption( "max", this.options.max ); + this._setOption( "min", this.options.min ); + this._setOption( "step", this.options.step ); + + // Only format if there is a value, prevents the field from being marked + // as invalid in Firefox, see #9573. + if ( this.value() !== "" ) { + + // Format the value, but don't constrain. + this._value( this.element.val(), true ); + } + + this._draw(); + this._on( this._events ); + this._refresh(); + + // Turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + } ); + }, + + _getCreateOptions: function() { + var options = this._super(); + var element = this.element; + + $.each( [ "min", "max", "step" ], function( i, option ) { + var value = element.attr( option ); + if ( value != null && value.length ) { + options[ option ] = value; + } + } ); + + return options; + }, + + _events: { + keydown: function( event ) { + if ( this._start( event ) && this._keydown( event ) ) { + event.preventDefault(); + } + }, + keyup: "_stop", + focus: function() { + this.previous = this.element.val(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + this._stop(); + this._refresh(); + if ( this.previous !== this.element.val() ) { + this._trigger( "change", event ); + } + }, + mousewheel: function( event, delta ) { + var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ); + var isActive = this.element[ 0 ] === activeElement; + + if ( !isActive || !delta ) { + return; + } + + if ( !this.spinning && !this._start( event ) ) { + return false; + } + + this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event ); + clearTimeout( this.mousewheelTimer ); + this.mousewheelTimer = this._delay( function() { + if ( this.spinning ) { + this._stop( event ); + } + }, 100 ); + event.preventDefault(); + }, + "mousedown .ui-spinner-button": function( event ) { + var previous; + + // We never want the buttons to have focus; whenever the user is + // interacting with the spinner, the focus should be on the input. + // If the input is focused then this.previous is properly set from + // when the input first received focus. If the input is not focused + // then we need to set this.previous based on the value before spinning. + previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ? + this.previous : this.element.val(); + function checkFocus() { + var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ); + if ( !isActive ) { + this.element.trigger( "focus" ); + this.previous = previous; + + // support: IE + // IE sets focus asynchronously, so we need to check if focus + // moved off of the input because the user clicked on the button. + this._delay( function() { + this.previous = previous; + } ); + } + } + + // Ensure focus is on (or stays on) the text field + event.preventDefault(); + checkFocus.call( this ); + + // Support: IE + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + // and check (again) if focus moved off of the input. + this.cancelBlur = true; + this._delay( function() { + delete this.cancelBlur; + checkFocus.call( this ); + } ); + + if ( this._start( event ) === false ) { + return; + } + + this._repeat( null, $( event.currentTarget ) + .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + "mouseup .ui-spinner-button": "_stop", + "mouseenter .ui-spinner-button": function( event ) { + + // button will add ui-state-active if mouse was down while mouseleave and kept down + if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { + return; + } + + if ( this._start( event ) === false ) { + return false; + } + this._repeat( null, $( event.currentTarget ) + .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + + // TODO: do we really want to consider this a stop? + // shouldn't we just stop the repeater and wait until mouseup before + // we trigger the stop event? + "mouseleave .ui-spinner-button": "_stop" + }, + + // Support mobile enhanced option and make backcompat more sane + _enhance: function() { + this.uiSpinner = this.element + .attr( "autocomplete", "off" ) + .wrap( "<span>" ) + .parent() + + // Add buttons + .append( + "<a></a><a></a>" + ); + }, + + _draw: function() { + this._enhance(); + + this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" ); + this._addClass( "ui-spinner-input" ); + + this.element.attr( "role", "spinbutton" ); + + // Button bindings + this.buttons = this.uiSpinner.children( "a" ) + .attr( "tabIndex", -1 ) + .attr( "aria-hidden", true ) + .button( { + classes: { + "ui-button": "" + } + } ); + + // TODO: Right now button does not support classes this is already updated in button PR + this._removeClass( this.buttons, "ui-corner-all" ); + + this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" ); + this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" ); + this.buttons.first().button( { + "icon": this.options.icons.up, + "showLabel": false + } ); + this.buttons.last().button( { + "icon": this.options.icons.down, + "showLabel": false + } ); + + // IE 6 doesn't understand height: 50% for the buttons + // unless the wrapper has an explicit height + if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) && + this.uiSpinner.height() > 0 ) { + this.uiSpinner.height( this.uiSpinner.height() ); + } + }, + + _keydown: function( event ) { + var options = this.options, + keyCode = $.ui.keyCode; + + switch ( event.keyCode ) { + case keyCode.UP: + this._repeat( null, 1, event ); + return true; + case keyCode.DOWN: + this._repeat( null, -1, event ); + return true; + case keyCode.PAGE_UP: + this._repeat( null, options.page, event ); + return true; + case keyCode.PAGE_DOWN: + this._repeat( null, -options.page, event ); + return true; + } + + return false; + }, + + _start: function( event ) { + if ( !this.spinning && this._trigger( "start", event ) === false ) { + return false; + } + + if ( !this.counter ) { + this.counter = 1; + } + this.spinning = true; + return true; + }, + + _repeat: function( i, steps, event ) { + i = i || 500; + + clearTimeout( this.timer ); + this.timer = this._delay( function() { + this._repeat( 40, steps, event ); + }, i ); + + this._spin( steps * this.options.step, event ); + }, + + _spin: function( step, event ) { + var value = this.value() || 0; + + if ( !this.counter ) { + this.counter = 1; + } + + value = this._adjustValue( value + step * this._increment( this.counter ) ); + + if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) { + this._value( value ); + this.counter++; + } + }, + + _increment: function( i ) { + var incremental = this.options.incremental; + + if ( incremental ) { + return typeof incremental === "function" ? + incremental( i ) : + Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 ); + } + + return 1; + }, + + _precision: function() { + var precision = this._precisionOf( this.options.step ); + if ( this.options.min !== null ) { + precision = Math.max( precision, this._precisionOf( this.options.min ) ); + } + return precision; + }, + + _precisionOf: function( num ) { + var str = num.toString(), + decimal = str.indexOf( "." ); + return decimal === -1 ? 0 : str.length - decimal - 1; + }, + + _adjustValue: function( value ) { + var base, aboveMin, + options = this.options; + + // Make sure we're at a valid step + // - find out where we are relative to the base (min or 0) + base = options.min !== null ? options.min : 0; + aboveMin = value - base; + + // - round to the nearest step + aboveMin = Math.round( aboveMin / options.step ) * options.step; + + // - rounding is based on 0, so adjust back to our base + value = base + aboveMin; + + // Fix precision from bad JS floating point math + value = parseFloat( value.toFixed( this._precision() ) ); + + // Clamp the value + if ( options.max !== null && value > options.max ) { + return options.max; + } + if ( options.min !== null && value < options.min ) { + return options.min; + } + + return value; + }, + + _stop: function( event ) { + if ( !this.spinning ) { + return; + } + + clearTimeout( this.timer ); + clearTimeout( this.mousewheelTimer ); + this.counter = 0; + this.spinning = false; + this._trigger( "stop", event ); + }, + + _setOption: function( key, value ) { + var prevValue, first, last; + + if ( key === "culture" || key === "numberFormat" ) { + prevValue = this._parse( this.element.val() ); + this.options[ key ] = value; + this.element.val( this._format( prevValue ) ); + return; + } + + if ( key === "max" || key === "min" || key === "step" ) { + if ( typeof value === "string" ) { + value = this._parse( value ); + } + } + if ( key === "icons" ) { + first = this.buttons.first().find( ".ui-icon" ); + this._removeClass( first, null, this.options.icons.up ); + this._addClass( first, null, value.up ); + last = this.buttons.last().find( ".ui-icon" ); + this._removeClass( last, null, this.options.icons.down ); + this._addClass( last, null, value.down ); + } + + this._super( key, value ); + }, + + _setOptionDisabled: function( value ) { + this._super( value ); + + this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value ); + this.element.prop( "disabled", !!value ); + this.buttons.button( value ? "disable" : "enable" ); + }, + + _setOptions: spinnerModifier( function( options ) { + this._super( options ); + } ), + + _parse: function( val ) { + if ( typeof val === "string" && val !== "" ) { + val = window.Globalize && this.options.numberFormat ? + Globalize.parseFloat( val, 10, this.options.culture ) : +val; + } + return val === "" || isNaN( val ) ? null : val; + }, + + _format: function( value ) { + if ( value === "" ) { + return ""; + } + return window.Globalize && this.options.numberFormat ? + Globalize.format( value, this.options.numberFormat, this.options.culture ) : + value; + }, + + _refresh: function() { + this.element.attr( { + "aria-valuemin": this.options.min, + "aria-valuemax": this.options.max, + + // TODO: what should we do with values that can't be parsed? + "aria-valuenow": this._parse( this.element.val() ) + } ); + }, + + isValid: function() { + var value = this.value(); + + // Null is invalid + if ( value === null ) { + return false; + } + + // If value gets adjusted, it's invalid + return value === this._adjustValue( value ); + }, + + // Update the value without triggering change + _value: function( value, allowAny ) { + var parsed; + if ( value !== "" ) { + parsed = this._parse( value ); + if ( parsed !== null ) { + if ( !allowAny ) { + parsed = this._adjustValue( parsed ); + } + value = this._format( parsed ); + } + } + this.element.val( value ); + this._refresh(); + }, + + _destroy: function() { + this.element + .prop( "disabled", false ) + .removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" ); + + this.uiSpinner.replaceWith( this.element ); + }, + + stepUp: spinnerModifier( function( steps ) { + this._stepUp( steps ); + } ), + _stepUp: function( steps ) { + if ( this._start() ) { + this._spin( ( steps || 1 ) * this.options.step ); + this._stop(); + } + }, + + stepDown: spinnerModifier( function( steps ) { + this._stepDown( steps ); + } ), + _stepDown: function( steps ) { + if ( this._start() ) { + this._spin( ( steps || 1 ) * -this.options.step ); + this._stop(); + } + }, + + pageUp: spinnerModifier( function( pages ) { + this._stepUp( ( pages || 1 ) * this.options.page ); + } ), + + pageDown: spinnerModifier( function( pages ) { + this._stepDown( ( pages || 1 ) * this.options.page ); + } ), + + value: function( newVal ) { + if ( !arguments.length ) { + return this._parse( this.element.val() ); + } + spinnerModifier( this._value ).call( this, newVal ); + }, + + widget: function() { + return this.uiSpinner; + } + } ); // DEPRECATED // TODO: switch return back to widget declaration at top of file when this is removed -if ( $.uiBackCompat !== false ) { - - // Backcompat for spinner html extension points - $.widget( "ui.spinner", $.ui.spinner, { - _enhance: function() { - this.uiSpinner = this.element - .attr( "autocomplete", "off" ) - .wrap( this._uiSpinnerHtml() ) - .parent() - - // Add buttons - .append( this._buttonHtml() ); - }, - _uiSpinnerHtml: function() { - return "<span>"; - }, - - _buttonHtml: function() { - return "<a></a><a></a>"; - } - } ); -} - -var widgetsSpinner = $.ui.spinner; - - -/*! - * jQuery UI Tabs 1.12.1 + if ( $.uiBackCompat !== false ) { + + // Backcompat for spinner html extension points + $.widget( "ui.spinner", $.ui.spinner, { + _enhance: function() { + this.uiSpinner = this.element + .attr( "autocomplete", "off" ) + .wrap( this._uiSpinnerHtml() ) + .parent() + + // Add buttons + .append( this._buttonHtml() ); + }, + _uiSpinnerHtml: function() { + return "<span>"; + }, + + _buttonHtml: function() { + return "<a></a><a></a>"; + } + } ); + } + + var widgetsSpinner = $.ui.spinner; + + + /*! + * jQuery UI Tabs 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14737,895 +15045,893 @@ var widgetsSpinner = $.ui.spinner; //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.tabs", { - version: "1.12.1", - delay: 300, - options: { - active: null, - classes: { - "ui-tabs": "ui-corner-all", - "ui-tabs-nav": "ui-corner-all", - "ui-tabs-panel": "ui-corner-bottom", - "ui-tabs-tab": "ui-corner-top" - }, - collapsible: false, - event: "click", - heightStyle: "content", - hide: null, - show: null, - - // Callbacks - activate: null, - beforeActivate: null, - beforeLoad: null, - load: null - }, - - _isLocal: ( function() { - var rhash = /#.*$/; - - return function( anchor ) { - var anchorUrl, locationUrl; - - anchorUrl = anchor.href.replace( rhash, "" ); - locationUrl = location.href.replace( rhash, "" ); - - // Decoding may throw an error if the URL isn't UTF-8 (#9518) - try { - anchorUrl = decodeURIComponent( anchorUrl ); - } catch ( error ) {} - try { - locationUrl = decodeURIComponent( locationUrl ); - } catch ( error ) {} - - return anchor.hash.length > 1 && anchorUrl === locationUrl; - }; - } )(), - - _create: function() { - var that = this, - options = this.options; - - this.running = false; - - this._addClass( "ui-tabs", "ui-widget ui-widget-content" ); - this._toggleClass( "ui-tabs-collapsible", null, options.collapsible ); - - this._processTabs(); - options.active = this._initialActive(); - - // Take disabling tabs via class attribute from HTML - // into account and update option properly. - if ( $.isArray( options.disabled ) ) { - options.disabled = $.unique( options.disabled.concat( - $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { - return that.tabs.index( li ); - } ) - ) ).sort(); - } - - // Check for length avoids error when initializing empty list - if ( this.options.active !== false && this.anchors.length ) { - this.active = this._findActive( options.active ); - } else { - this.active = $(); - } - - this._refresh(); - - if ( this.active.length ) { - this.load( options.active ); - } - }, - - _initialActive: function() { - var active = this.options.active, - collapsible = this.options.collapsible, - locationHash = location.hash.substring( 1 ); - - if ( active === null ) { - - // check the fragment identifier in the URL - if ( locationHash ) { - this.tabs.each( function( i, tab ) { - if ( $( tab ).attr( "aria-controls" ) === locationHash ) { - active = i; - return false; - } - } ); - } - - // Check for a tab marked active via a class - if ( active === null ) { - active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); - } - - // No active tab, set to false - if ( active === null || active === -1 ) { - active = this.tabs.length ? 0 : false; - } - } - - // Handle numbers: negative, out of range - if ( active !== false ) { - active = this.tabs.index( this.tabs.eq( active ) ); - if ( active === -1 ) { - active = collapsible ? false : 0; - } - } - - // Don't allow collapsible: false and active: false - if ( !collapsible && active === false && this.anchors.length ) { - active = 0; - } - - return active; - }, - - _getCreateEventData: function() { - return { - tab: this.active, - panel: !this.active.length ? $() : this._getPanelForTab( this.active ) - }; - }, - - _tabKeydown: function( event ) { - var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ), - selectedIndex = this.tabs.index( focusedTab ), - goingForward = true; - - if ( this._handlePageNav( event ) ) { - return; - } - - switch ( event.keyCode ) { - case $.ui.keyCode.RIGHT: - case $.ui.keyCode.DOWN: - selectedIndex++; - break; - case $.ui.keyCode.UP: - case $.ui.keyCode.LEFT: - goingForward = false; - selectedIndex--; - break; - case $.ui.keyCode.END: - selectedIndex = this.anchors.length - 1; - break; - case $.ui.keyCode.HOME: - selectedIndex = 0; - break; - case $.ui.keyCode.SPACE: - - // Activate only, no collapsing - event.preventDefault(); - clearTimeout( this.activating ); - this._activate( selectedIndex ); - return; - case $.ui.keyCode.ENTER: - - // Toggle (cancel delayed activation, allow collapsing) - event.preventDefault(); - clearTimeout( this.activating ); - - // Determine if we should collapse or activate - this._activate( selectedIndex === this.options.active ? false : selectedIndex ); - return; - default: - return; - } - - // Focus the appropriate tab, based on which key was pressed - event.preventDefault(); - clearTimeout( this.activating ); - selectedIndex = this._focusNextTab( selectedIndex, goingForward ); - - // Navigating with control/command key will prevent automatic activation - if ( !event.ctrlKey && !event.metaKey ) { - - // Update aria-selected immediately so that AT think the tab is already selected. - // Otherwise AT may confuse the user by stating that they need to activate the tab, - // but the tab will already be activated by the time the announcement finishes. - focusedTab.attr( "aria-selected", "false" ); - this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); - - this.activating = this._delay( function() { - this.option( "active", selectedIndex ); - }, this.delay ); - } - }, - - _panelKeydown: function( event ) { - if ( this._handlePageNav( event ) ) { - return; - } - - // Ctrl+up moves focus to the current tab - if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { - event.preventDefault(); - this.active.trigger( "focus" ); - } - }, - - // Alt+page up/down moves focus to the previous/next tab (and activates) - _handlePageNav: function( event ) { - if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { - this._activate( this._focusNextTab( this.options.active - 1, false ) ); - return true; - } - if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { - this._activate( this._focusNextTab( this.options.active + 1, true ) ); - return true; - } - }, - - _findNextTab: function( index, goingForward ) { - var lastTabIndex = this.tabs.length - 1; - - function constrain() { - if ( index > lastTabIndex ) { - index = 0; - } - if ( index < 0 ) { - index = lastTabIndex; - } - return index; - } - - while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { - index = goingForward ? index + 1 : index - 1; - } - - return index; - }, - - _focusNextTab: function( index, goingForward ) { - index = this._findNextTab( index, goingForward ); - this.tabs.eq( index ).trigger( "focus" ); - return index; - }, - - _setOption: function( key, value ) { - if ( key === "active" ) { - - // _activate() will handle invalid values and update this.options - this._activate( value ); - return; - } - - this._super( key, value ); - - if ( key === "collapsible" ) { - this._toggleClass( "ui-tabs-collapsible", null, value ); - - // Setting collapsible: false while collapsed; open first panel - if ( !value && this.options.active === false ) { - this._activate( 0 ); - } - } - - if ( key === "event" ) { - this._setupEvents( value ); - } - - if ( key === "heightStyle" ) { - this._setupHeightStyle( value ); - } - }, - - _sanitizeSelector: function( hash ) { - return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; - }, - - refresh: function() { - var options = this.options, - lis = this.tablist.children( ":has(a[href])" ); - - // Get disabled tabs from class attribute from HTML - // this will get converted to a boolean if needed in _refresh() - options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { - return lis.index( tab ); - } ); - - this._processTabs(); - - // Was collapsed or no tabs - if ( options.active === false || !this.anchors.length ) { - options.active = false; - this.active = $(); - - // was active, but active tab is gone - } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { - - // all remaining tabs are disabled - if ( this.tabs.length === options.disabled.length ) { - options.active = false; - this.active = $(); - - // activate previous tab - } else { - this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); - } - - // was active, active tab still exists - } else { - - // make sure active index is correct - options.active = this.tabs.index( this.active ); - } - - this._refresh(); - }, - - _refresh: function() { - this._setOptionDisabled( this.options.disabled ); - this._setupEvents( this.options.event ); - this._setupHeightStyle( this.options.heightStyle ); - - this.tabs.not( this.active ).attr( { - "aria-selected": "false", - "aria-expanded": "false", - tabIndex: -1 - } ); - this.panels.not( this._getPanelForTab( this.active ) ) - .hide() - .attr( { - "aria-hidden": "true" - } ); - - // Make sure one tab is in the tab order - if ( !this.active.length ) { - this.tabs.eq( 0 ).attr( "tabIndex", 0 ); - } else { - this.active - .attr( { - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - } ); - this._addClass( this.active, "ui-tabs-active", "ui-state-active" ); - this._getPanelForTab( this.active ) - .show() - .attr( { - "aria-hidden": "false" - } ); - } - }, - - _processTabs: function() { - var that = this, - prevTabs = this.tabs, - prevAnchors = this.anchors, - prevPanels = this.panels; - - this.tablist = this._getList().attr( "role", "tablist" ); - this._addClass( this.tablist, "ui-tabs-nav", - "ui-helper-reset ui-helper-clearfix ui-widget-header" ); - - // Prevent users from focusing disabled tabs via click - this.tablist - .on( "mousedown" + this.eventNamespace, "> li", function( event ) { - if ( $( this ).is( ".ui-state-disabled" ) ) { - event.preventDefault(); - } - } ) - - // Support: IE <9 - // Preventing the default action in mousedown doesn't prevent IE - // from focusing the element, so if the anchor gets focused, blur. - // We don't have to worry about focusing the previously focused - // element since clicking on a non-focusable element should focus - // the body anyway. - .on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() { - if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { - this.blur(); - } - } ); - - this.tabs = this.tablist.find( "> li:has(a[href])" ) - .attr( { - role: "tab", - tabIndex: -1 - } ); - this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" ); - - this.anchors = this.tabs.map( function() { - return $( "a", this )[ 0 ]; - } ) - .attr( { - role: "presentation", - tabIndex: -1 - } ); - this._addClass( this.anchors, "ui-tabs-anchor" ); - - this.panels = $(); - - this.anchors.each( function( i, anchor ) { - var selector, panel, panelId, - anchorId = $( anchor ).uniqueId().attr( "id" ), - tab = $( anchor ).closest( "li" ), - originalAriaControls = tab.attr( "aria-controls" ); - - // Inline tab - if ( that._isLocal( anchor ) ) { - selector = anchor.hash; - panelId = selector.substring( 1 ); - panel = that.element.find( that._sanitizeSelector( selector ) ); - - // remote tab - } else { - - // If the tab doesn't already have aria-controls, - // generate an id by using a throw-away element - panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id; - selector = "#" + panelId; - panel = that.element.find( selector ); - if ( !panel.length ) { - panel = that._createPanel( panelId ); - panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); - } - panel.attr( "aria-live", "polite" ); - } - - if ( panel.length ) { - that.panels = that.panels.add( panel ); - } - if ( originalAriaControls ) { - tab.data( "ui-tabs-aria-controls", originalAriaControls ); - } - tab.attr( { - "aria-controls": panelId, - "aria-labelledby": anchorId - } ); - panel.attr( "aria-labelledby", anchorId ); - } ); - - this.panels.attr( "role", "tabpanel" ); - this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" ); - - // Avoid memory leaks (#10056) - if ( prevTabs ) { - this._off( prevTabs.not( this.tabs ) ); - this._off( prevAnchors.not( this.anchors ) ); - this._off( prevPanels.not( this.panels ) ); - } - }, - - // Allow overriding how to find the list for rare usage scenarios (#7715) - _getList: function() { - return this.tablist || this.element.find( "ol, ul" ).eq( 0 ); - }, - - _createPanel: function( id ) { - return $( "<div>" ) - .attr( "id", id ) - .data( "ui-tabs-destroy", true ); - }, - - _setOptionDisabled: function( disabled ) { - var currentItem, li, i; - - if ( $.isArray( disabled ) ) { - if ( !disabled.length ) { - disabled = false; - } else if ( disabled.length === this.anchors.length ) { - disabled = true; - } - } - - // Disable tabs - for ( i = 0; ( li = this.tabs[ i ] ); i++ ) { - currentItem = $( li ); - if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { - currentItem.attr( "aria-disabled", "true" ); - this._addClass( currentItem, null, "ui-state-disabled" ); - } else { - currentItem.removeAttr( "aria-disabled" ); - this._removeClass( currentItem, null, "ui-state-disabled" ); - } - } - - this.options.disabled = disabled; - - this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, - disabled === true ); - }, - - _setupEvents: function( event ) { - var events = {}; - if ( event ) { - $.each( event.split( " " ), function( index, eventName ) { - events[ eventName ] = "_eventHandler"; - } ); - } - - this._off( this.anchors.add( this.tabs ).add( this.panels ) ); - - // Always prevent the default action, even when disabled - this._on( true, this.anchors, { - click: function( event ) { - event.preventDefault(); - } - } ); - this._on( this.anchors, events ); - this._on( this.tabs, { keydown: "_tabKeydown" } ); - this._on( this.panels, { keydown: "_panelKeydown" } ); - - this._focusable( this.tabs ); - this._hoverable( this.tabs ); - }, - - _setupHeightStyle: function( heightStyle ) { - var maxHeight, - parent = this.element.parent(); - - if ( heightStyle === "fill" ) { - maxHeight = parent.height(); - maxHeight -= this.element.outerHeight() - this.element.height(); - - this.element.siblings( ":visible" ).each( function() { - var elem = $( this ), - position = elem.css( "position" ); - - if ( position === "absolute" || position === "fixed" ) { - return; - } - maxHeight -= elem.outerHeight( true ); - } ); - - this.element.children().not( this.panels ).each( function() { - maxHeight -= $( this ).outerHeight( true ); - } ); - - this.panels.each( function() { - $( this ).height( Math.max( 0, maxHeight - - $( this ).innerHeight() + $( this ).height() ) ); - } ) - .css( "overflow", "auto" ); - } else if ( heightStyle === "auto" ) { - maxHeight = 0; - this.panels.each( function() { - maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); - } ).height( maxHeight ); - } - }, - - _eventHandler: function( event ) { - var options = this.options, - active = this.active, - anchor = $( event.currentTarget ), - tab = anchor.closest( "li" ), - clickedIsActive = tab[ 0 ] === active[ 0 ], - collapsing = clickedIsActive && options.collapsible, - toShow = collapsing ? $() : this._getPanelForTab( tab ), - toHide = !active.length ? $() : this._getPanelForTab( active ), - eventData = { - oldTab: active, - oldPanel: toHide, - newTab: collapsing ? $() : tab, - newPanel: toShow - }; - - event.preventDefault(); - - if ( tab.hasClass( "ui-state-disabled" ) || - - // tab is already loading - tab.hasClass( "ui-tabs-loading" ) || - - // can't switch durning an animation - this.running || - - // click on active header, but not collapsible - ( clickedIsActive && !options.collapsible ) || - - // allow canceling activation - ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { - return; - } - - options.active = collapsing ? false : this.tabs.index( tab ); - - this.active = clickedIsActive ? $() : tab; - if ( this.xhr ) { - this.xhr.abort(); - } - - if ( !toHide.length && !toShow.length ) { - $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); - } - - if ( toShow.length ) { - this.load( this.tabs.index( tab ), event ); - } - this._toggle( event, eventData ); - }, - - // Handles show/hide for selecting tabs - _toggle: function( event, eventData ) { - var that = this, - toShow = eventData.newPanel, - toHide = eventData.oldPanel; - - this.running = true; - - function complete() { - that.running = false; - that._trigger( "activate", event, eventData ); - } - - function show() { - that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" ); - - if ( toShow.length && that.options.show ) { - that._show( toShow, that.options.show, complete ); - } else { - toShow.show(); - complete(); - } - } - - // Start out by hiding, then showing, then completing - if ( toHide.length && this.options.hide ) { - this._hide( toHide, this.options.hide, function() { - that._removeClass( eventData.oldTab.closest( "li" ), - "ui-tabs-active", "ui-state-active" ); - show(); - } ); - } else { - this._removeClass( eventData.oldTab.closest( "li" ), - "ui-tabs-active", "ui-state-active" ); - toHide.hide(); - show(); - } - - toHide.attr( "aria-hidden", "true" ); - eventData.oldTab.attr( { - "aria-selected": "false", - "aria-expanded": "false" - } ); - - // If we're switching tabs, remove the old tab from the tab order. - // If we're opening from collapsed state, remove the previous tab from the tab order. - // If we're collapsing, then keep the collapsing tab in the tab order. - if ( toShow.length && toHide.length ) { - eventData.oldTab.attr( "tabIndex", -1 ); - } else if ( toShow.length ) { - this.tabs.filter( function() { - return $( this ).attr( "tabIndex" ) === 0; - } ) - .attr( "tabIndex", -1 ); - } - - toShow.attr( "aria-hidden", "false" ); - eventData.newTab.attr( { - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - } ); - }, - - _activate: function( index ) { - var anchor, - active = this._findActive( index ); - - // Trying to activate the already active panel - if ( active[ 0 ] === this.active[ 0 ] ) { - return; - } - - // Trying to collapse, simulate a click on the current active header - if ( !active.length ) { - active = this.active; - } - - anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; - this._eventHandler( { - target: anchor, - currentTarget: anchor, - preventDefault: $.noop - } ); - }, - - _findActive: function( index ) { - return index === false ? $() : this.tabs.eq( index ); - }, - - _getIndex: function( index ) { - - // meta-function to give users option to provide a href string instead of a numerical index. - if ( typeof index === "string" ) { - index = this.anchors.index( this.anchors.filter( "[href$='" + - $.ui.escapeSelector( index ) + "']" ) ); - } - - return index; - }, - - _destroy: function() { - if ( this.xhr ) { - this.xhr.abort(); - } - - this.tablist - .removeAttr( "role" ) - .off( this.eventNamespace ); - - this.anchors - .removeAttr( "role tabIndex" ) - .removeUniqueId(); - - this.tabs.add( this.panels ).each( function() { - if ( $.data( this, "ui-tabs-destroy" ) ) { - $( this ).remove(); - } else { - $( this ).removeAttr( "role tabIndex " + - "aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" ); - } - } ); - - this.tabs.each( function() { - var li = $( this ), - prev = li.data( "ui-tabs-aria-controls" ); - if ( prev ) { - li - .attr( "aria-controls", prev ) - .removeData( "ui-tabs-aria-controls" ); - } else { - li.removeAttr( "aria-controls" ); - } - } ); - - this.panels.show(); - - if ( this.options.heightStyle !== "content" ) { - this.panels.css( "height", "" ); - } - }, - - enable: function( index ) { - var disabled = this.options.disabled; - if ( disabled === false ) { - return; - } - - if ( index === undefined ) { - disabled = false; - } else { - index = this._getIndex( index ); - if ( $.isArray( disabled ) ) { - disabled = $.map( disabled, function( num ) { - return num !== index ? num : null; - } ); - } else { - disabled = $.map( this.tabs, function( li, num ) { - return num !== index ? num : null; - } ); - } - } - this._setOptionDisabled( disabled ); - }, - - disable: function( index ) { - var disabled = this.options.disabled; - if ( disabled === true ) { - return; - } - - if ( index === undefined ) { - disabled = true; - } else { - index = this._getIndex( index ); - if ( $.inArray( index, disabled ) !== -1 ) { - return; - } - if ( $.isArray( disabled ) ) { - disabled = $.merge( [ index ], disabled ).sort(); - } else { - disabled = [ index ]; - } - } - this._setOptionDisabled( disabled ); - }, - - load: function( index, event ) { - index = this._getIndex( index ); - var that = this, - tab = this.tabs.eq( index ), - anchor = tab.find( ".ui-tabs-anchor" ), - panel = this._getPanelForTab( tab ), - eventData = { - tab: tab, - panel: panel - }, - complete = function( jqXHR, status ) { - if ( status === "abort" ) { - that.panels.stop( false, true ); - } - - that._removeClass( tab, "ui-tabs-loading" ); - panel.removeAttr( "aria-busy" ); - - if ( jqXHR === that.xhr ) { - delete that.xhr; - } - }; - - // Not remote - if ( this._isLocal( anchor[ 0 ] ) ) { - return; - } - - this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); - - // Support: jQuery <1.8 - // jQuery <1.8 returns false if the request is canceled in beforeSend, - // but as of 1.8, $.ajax() always returns a jqXHR object. - if ( this.xhr && this.xhr.statusText !== "canceled" ) { - this._addClass( tab, "ui-tabs-loading" ); - panel.attr( "aria-busy", "true" ); - - this.xhr - .done( function( response, status, jqXHR ) { - - // support: jQuery <1.8 - // http://bugs.jquery.com/ticket/11778 - setTimeout( function() { - panel.html( response ); - that._trigger( "load", event, eventData ); - - complete( jqXHR, status ); - }, 1 ); - } ) - .fail( function( jqXHR, status ) { - - // support: jQuery <1.8 - // http://bugs.jquery.com/ticket/11778 - setTimeout( function() { - complete( jqXHR, status ); - }, 1 ); - } ); - } - }, - - _ajaxSettings: function( anchor, event, eventData ) { - var that = this; - return { - - // Support: IE <11 only - // Strip any hash that exists to prevent errors with the Ajax request - url: anchor.attr( "href" ).replace( /#.*$/, "" ), - beforeSend: function( jqXHR, settings ) { - return that._trigger( "beforeLoad", event, - $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) ); - } - }; - }, - - _getPanelForTab: function( tab ) { - var id = $( tab ).attr( "aria-controls" ); - return this.element.find( this._sanitizeSelector( "#" + id ) ); - } -} ); + $.widget( "ui.tabs", { + version: "1.13.0", + delay: 300, + options: { + active: null, + classes: { + "ui-tabs": "ui-corner-all", + "ui-tabs-nav": "ui-corner-all", + "ui-tabs-panel": "ui-corner-bottom", + "ui-tabs-tab": "ui-corner-top" + }, + collapsible: false, + event: "click", + heightStyle: "content", + hide: null, + show: null, + + // Callbacks + activate: null, + beforeActivate: null, + beforeLoad: null, + load: null + }, + + _isLocal: ( function() { + var rhash = /#.*$/; + + return function( anchor ) { + var anchorUrl, locationUrl; + + anchorUrl = anchor.href.replace( rhash, "" ); + locationUrl = location.href.replace( rhash, "" ); + + // Decoding may throw an error if the URL isn't UTF-8 (#9518) + try { + anchorUrl = decodeURIComponent( anchorUrl ); + } catch ( error ) {} + try { + locationUrl = decodeURIComponent( locationUrl ); + } catch ( error ) {} + + return anchor.hash.length > 1 && anchorUrl === locationUrl; + }; + } )(), + + _create: function() { + var that = this, + options = this.options; + + this.running = false; + + this._addClass( "ui-tabs", "ui-widget ui-widget-content" ); + this._toggleClass( "ui-tabs-collapsible", null, options.collapsible ); + + this._processTabs(); + options.active = this._initialActive(); + + // Take disabling tabs via class attribute from HTML + // into account and update option properly. + if ( Array.isArray( options.disabled ) ) { + options.disabled = $.uniqueSort( options.disabled.concat( + $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { + return that.tabs.index( li ); + } ) + ) ).sort(); + } + + // Check for length avoids error when initializing empty list + if ( this.options.active !== false && this.anchors.length ) { + this.active = this._findActive( options.active ); + } else { + this.active = $(); + } + + this._refresh(); + + if ( this.active.length ) { + this.load( options.active ); + } + }, + + _initialActive: function() { + var active = this.options.active, + collapsible = this.options.collapsible, + locationHash = location.hash.substring( 1 ); + + if ( active === null ) { + + // check the fragment identifier in the URL + if ( locationHash ) { + this.tabs.each( function( i, tab ) { + if ( $( tab ).attr( "aria-controls" ) === locationHash ) { + active = i; + return false; + } + } ); + } + + // Check for a tab marked active via a class + if ( active === null ) { + active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); + } + + // No active tab, set to false + if ( active === null || active === -1 ) { + active = this.tabs.length ? 0 : false; + } + } + + // Handle numbers: negative, out of range + if ( active !== false ) { + active = this.tabs.index( this.tabs.eq( active ) ); + if ( active === -1 ) { + active = collapsible ? false : 0; + } + } + + // Don't allow collapsible: false and active: false + if ( !collapsible && active === false && this.anchors.length ) { + active = 0; + } + + return active; + }, + + _getCreateEventData: function() { + return { + tab: this.active, + panel: !this.active.length ? $() : this._getPanelForTab( this.active ) + }; + }, + + _tabKeydown: function( event ) { + var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ), + selectedIndex = this.tabs.index( focusedTab ), + goingForward = true; + + if ( this._handlePageNav( event ) ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + selectedIndex++; + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.LEFT: + goingForward = false; + selectedIndex--; + break; + case $.ui.keyCode.END: + selectedIndex = this.anchors.length - 1; + break; + case $.ui.keyCode.HOME: + selectedIndex = 0; + break; + case $.ui.keyCode.SPACE: + + // Activate only, no collapsing + event.preventDefault(); + clearTimeout( this.activating ); + this._activate( selectedIndex ); + return; + case $.ui.keyCode.ENTER: + + // Toggle (cancel delayed activation, allow collapsing) + event.preventDefault(); + clearTimeout( this.activating ); + + // Determine if we should collapse or activate + this._activate( selectedIndex === this.options.active ? false : selectedIndex ); + return; + default: + return; + } + + // Focus the appropriate tab, based on which key was pressed + event.preventDefault(); + clearTimeout( this.activating ); + selectedIndex = this._focusNextTab( selectedIndex, goingForward ); + + // Navigating with control/command key will prevent automatic activation + if ( !event.ctrlKey && !event.metaKey ) { + + // Update aria-selected immediately so that AT think the tab is already selected. + // Otherwise AT may confuse the user by stating that they need to activate the tab, + // but the tab will already be activated by the time the announcement finishes. + focusedTab.attr( "aria-selected", "false" ); + this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); + + this.activating = this._delay( function() { + this.option( "active", selectedIndex ); + }, this.delay ); + } + }, + + _panelKeydown: function( event ) { + if ( this._handlePageNav( event ) ) { + return; + } + + // Ctrl+up moves focus to the current tab + if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { + event.preventDefault(); + this.active.trigger( "focus" ); + } + }, + + // Alt+page up/down moves focus to the previous/next tab (and activates) + _handlePageNav: function( event ) { + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { + this._activate( this._focusNextTab( this.options.active - 1, false ) ); + return true; + } + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { + this._activate( this._focusNextTab( this.options.active + 1, true ) ); + return true; + } + }, + + _findNextTab: function( index, goingForward ) { + var lastTabIndex = this.tabs.length - 1; + + function constrain() { + if ( index > lastTabIndex ) { + index = 0; + } + if ( index < 0 ) { + index = lastTabIndex; + } + return index; + } + + while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { + index = goingForward ? index + 1 : index - 1; + } + + return index; + }, + + _focusNextTab: function( index, goingForward ) { + index = this._findNextTab( index, goingForward ); + this.tabs.eq( index ).trigger( "focus" ); + return index; + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + this._super( key, value ); + + if ( key === "collapsible" ) { + this._toggleClass( "ui-tabs-collapsible", null, value ); + + // Setting collapsible: false while collapsed; open first panel + if ( !value && this.options.active === false ) { + this._activate( 0 ); + } + } + + if ( key === "event" ) { + this._setupEvents( value ); + } + + if ( key === "heightStyle" ) { + this._setupHeightStyle( value ); + } + }, + + _sanitizeSelector: function( hash ) { + return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; + }, + + refresh: function() { + var options = this.options, + lis = this.tablist.children( ":has(a[href])" ); + + // Get disabled tabs from class attribute from HTML + // this will get converted to a boolean if needed in _refresh() + options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { + return lis.index( tab ); + } ); + + this._processTabs(); + + // Was collapsed or no tabs + if ( options.active === false || !this.anchors.length ) { + options.active = false; + this.active = $(); + + // was active, but active tab is gone + } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { + + // all remaining tabs are disabled + if ( this.tabs.length === options.disabled.length ) { + options.active = false; + this.active = $(); + + // activate previous tab + } else { + this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); + } + + // was active, active tab still exists + } else { + + // make sure active index is correct + options.active = this.tabs.index( this.active ); + } + + this._refresh(); + }, + + _refresh: function() { + this._setOptionDisabled( this.options.disabled ); + this._setupEvents( this.options.event ); + this._setupHeightStyle( this.options.heightStyle ); + + this.tabs.not( this.active ).attr( { + "aria-selected": "false", + "aria-expanded": "false", + tabIndex: -1 + } ); + this.panels.not( this._getPanelForTab( this.active ) ) + .hide() + .attr( { + "aria-hidden": "true" + } ); + + // Make sure one tab is in the tab order + if ( !this.active.length ) { + this.tabs.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active + .attr( { + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + } ); + this._addClass( this.active, "ui-tabs-active", "ui-state-active" ); + this._getPanelForTab( this.active ) + .show() + .attr( { + "aria-hidden": "false" + } ); + } + }, + + _processTabs: function() { + var that = this, + prevTabs = this.tabs, + prevAnchors = this.anchors, + prevPanels = this.panels; + + this.tablist = this._getList().attr( "role", "tablist" ); + this._addClass( this.tablist, "ui-tabs-nav", + "ui-helper-reset ui-helper-clearfix ui-widget-header" ); + + // Prevent users from focusing disabled tabs via click + this.tablist + .on( "mousedown" + this.eventNamespace, "> li", function( event ) { + if ( $( this ).is( ".ui-state-disabled" ) ) { + event.preventDefault(); + } + } ) + + // Support: IE <9 + // Preventing the default action in mousedown doesn't prevent IE + // from focusing the element, so if the anchor gets focused, blur. + // We don't have to worry about focusing the previously focused + // element since clicking on a non-focusable element should focus + // the body anyway. + .on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() { + if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { + this.blur(); + } + } ); + + this.tabs = this.tablist.find( "> li:has(a[href])" ) + .attr( { + role: "tab", + tabIndex: -1 + } ); + this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" ); + + this.anchors = this.tabs.map( function() { + return $( "a", this )[ 0 ]; + } ) + .attr( { + tabIndex: -1 + } ); + this._addClass( this.anchors, "ui-tabs-anchor" ); + + this.panels = $(); + + this.anchors.each( function( i, anchor ) { + var selector, panel, panelId, + anchorId = $( anchor ).uniqueId().attr( "id" ), + tab = $( anchor ).closest( "li" ), + originalAriaControls = tab.attr( "aria-controls" ); + + // Inline tab + if ( that._isLocal( anchor ) ) { + selector = anchor.hash; + panelId = selector.substring( 1 ); + panel = that.element.find( that._sanitizeSelector( selector ) ); + + // remote tab + } else { + + // If the tab doesn't already have aria-controls, + // generate an id by using a throw-away element + panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id; + selector = "#" + panelId; + panel = that.element.find( selector ); + if ( !panel.length ) { + panel = that._createPanel( panelId ); + panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + } + panel.attr( "aria-live", "polite" ); + } + + if ( panel.length ) { + that.panels = that.panels.add( panel ); + } + if ( originalAriaControls ) { + tab.data( "ui-tabs-aria-controls", originalAriaControls ); + } + tab.attr( { + "aria-controls": panelId, + "aria-labelledby": anchorId + } ); + panel.attr( "aria-labelledby", anchorId ); + } ); + + this.panels.attr( "role", "tabpanel" ); + this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" ); + + // Avoid memory leaks (#10056) + if ( prevTabs ) { + this._off( prevTabs.not( this.tabs ) ); + this._off( prevAnchors.not( this.anchors ) ); + this._off( prevPanels.not( this.panels ) ); + } + }, + + // Allow overriding how to find the list for rare usage scenarios (#7715) + _getList: function() { + return this.tablist || this.element.find( "ol, ul" ).eq( 0 ); + }, + + _createPanel: function( id ) { + return $( "<div>" ) + .attr( "id", id ) + .data( "ui-tabs-destroy", true ); + }, + + _setOptionDisabled: function( disabled ) { + var currentItem, li, i; + + if ( Array.isArray( disabled ) ) { + if ( !disabled.length ) { + disabled = false; + } else if ( disabled.length === this.anchors.length ) { + disabled = true; + } + } + + // Disable tabs + for ( i = 0; ( li = this.tabs[ i ] ); i++ ) { + currentItem = $( li ); + if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { + currentItem.attr( "aria-disabled", "true" ); + this._addClass( currentItem, null, "ui-state-disabled" ); + } else { + currentItem.removeAttr( "aria-disabled" ); + this._removeClass( currentItem, null, "ui-state-disabled" ); + } + } + + this.options.disabled = disabled; + + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, + disabled === true ); + }, + + _setupEvents: function( event ) { + var events = {}; + if ( event ) { + $.each( event.split( " " ), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + } ); + } + + this._off( this.anchors.add( this.tabs ).add( this.panels ) ); + + // Always prevent the default action, even when disabled + this._on( true, this.anchors, { + click: function( event ) { + event.preventDefault(); + } + } ); + this._on( this.anchors, events ); + this._on( this.tabs, { keydown: "_tabKeydown" } ); + this._on( this.panels, { keydown: "_panelKeydown" } ); + + this._focusable( this.tabs ); + this._hoverable( this.tabs ); + }, + + _setupHeightStyle: function( heightStyle ) { + var maxHeight, + parent = this.element.parent(); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + maxHeight -= this.element.outerHeight() - this.element.height(); + + this.element.siblings( ":visible" ).each( function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + } ); + + this.element.children().not( this.panels ).each( function() { + maxHeight -= $( this ).outerHeight( true ); + } ); + + this.panels.each( function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + } ) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.panels.each( function() { + maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); + } ).height( maxHeight ); + } + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + anchor = $( event.currentTarget ), + tab = anchor.closest( "li" ), + clickedIsActive = tab[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : this._getPanelForTab( tab ), + toHide = !active.length ? $() : this._getPanelForTab( active ), + eventData = { + oldTab: active, + oldPanel: toHide, + newTab: collapsing ? $() : tab, + newPanel: toShow + }; + + event.preventDefault(); + + if ( tab.hasClass( "ui-state-disabled" ) || + + // tab is already loading + tab.hasClass( "ui-tabs-loading" ) || + + // can't switch durning an animation + this.running || + + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.tabs.index( tab ); + + this.active = clickedIsActive ? $() : tab; + if ( this.xhr ) { + this.xhr.abort(); + } + + if ( !toHide.length && !toShow.length ) { + $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); + } + + if ( toShow.length ) { + this.load( this.tabs.index( tab ), event ); + } + this._toggle( event, eventData ); + }, + + // Handles show/hide for selecting tabs + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel; + + this.running = true; + + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } + + function show() { + that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" ); + + if ( toShow.length && that.options.show ) { + that._show( toShow, that.options.show, complete ); + } else { + toShow.show(); + complete(); + } + } + + // Start out by hiding, then showing, then completing + if ( toHide.length && this.options.hide ) { + this._hide( toHide, this.options.hide, function() { + that._removeClass( eventData.oldTab.closest( "li" ), + "ui-tabs-active", "ui-state-active" ); + show(); + } ); + } else { + this._removeClass( eventData.oldTab.closest( "li" ), + "ui-tabs-active", "ui-state-active" ); + toHide.hide(); + show(); + } + + toHide.attr( "aria-hidden", "true" ); + eventData.oldTab.attr( { + "aria-selected": "false", + "aria-expanded": "false" + } ); + + // If we're switching tabs, remove the old tab from the tab order. + // If we're opening from collapsed state, remove the previous tab from the tab order. + // If we're collapsing, then keep the collapsing tab in the tab order. + if ( toShow.length && toHide.length ) { + eventData.oldTab.attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.tabs.filter( function() { + return $( this ).attr( "tabIndex" ) === 0; + } ) + .attr( "tabIndex", -1 ); + } + + toShow.attr( "aria-hidden", "false" ); + eventData.newTab.attr( { + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + } ); + }, + + _activate: function( index ) { + var anchor, + active = this._findActive( index ); + + // Trying to activate the already active panel + if ( active[ 0 ] === this.active[ 0 ] ) { + return; + } + + // Trying to collapse, simulate a click on the current active header + if ( !active.length ) { + active = this.active; + } + + anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; + this._eventHandler( { + target: anchor, + currentTarget: anchor, + preventDefault: $.noop + } ); + }, + + _findActive: function( index ) { + return index === false ? $() : this.tabs.eq( index ); + }, + + _getIndex: function( index ) { + + // meta-function to give users option to provide a href string instead of a numerical index. + if ( typeof index === "string" ) { + index = this.anchors.index( this.anchors.filter( "[href$='" + + $.escapeSelector( index ) + "']" ) ); + } + + return index; + }, + + _destroy: function() { + if ( this.xhr ) { + this.xhr.abort(); + } + + this.tablist + .removeAttr( "role" ) + .off( this.eventNamespace ); + + this.anchors + .removeAttr( "role tabIndex" ) + .removeUniqueId(); + + this.tabs.add( this.panels ).each( function() { + if ( $.data( this, "ui-tabs-destroy" ) ) { + $( this ).remove(); + } else { + $( this ).removeAttr( "role tabIndex " + + "aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" ); + } + } ); + + this.tabs.each( function() { + var li = $( this ), + prev = li.data( "ui-tabs-aria-controls" ); + if ( prev ) { + li + .attr( "aria-controls", prev ) + .removeData( "ui-tabs-aria-controls" ); + } else { + li.removeAttr( "aria-controls" ); + } + } ); + + this.panels.show(); + + if ( this.options.heightStyle !== "content" ) { + this.panels.css( "height", "" ); + } + }, + + enable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === false ) { + return; + } + + if ( index === undefined ) { + disabled = false; + } else { + index = this._getIndex( index ); + if ( Array.isArray( disabled ) ) { + disabled = $.map( disabled, function( num ) { + return num !== index ? num : null; + } ); + } else { + disabled = $.map( this.tabs, function( li, num ) { + return num !== index ? num : null; + } ); + } + } + this._setOptionDisabled( disabled ); + }, + + disable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === true ) { + return; + } + + if ( index === undefined ) { + disabled = true; + } else { + index = this._getIndex( index ); + if ( $.inArray( index, disabled ) !== -1 ) { + return; + } + if ( Array.isArray( disabled ) ) { + disabled = $.merge( [ index ], disabled ).sort(); + } else { + disabled = [ index ]; + } + } + this._setOptionDisabled( disabled ); + }, + + load: function( index, event ) { + index = this._getIndex( index ); + var that = this, + tab = this.tabs.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), + eventData = { + tab: tab, + panel: panel + }, + complete = function( jqXHR, status ) { + if ( status === "abort" ) { + that.panels.stop( false, true ); + } + + that._removeClass( tab, "ui-tabs-loading" ); + panel.removeAttr( "aria-busy" ); + + if ( jqXHR === that.xhr ) { + delete that.xhr; + } + }; + + // Not remote + if ( this._isLocal( anchor[ 0 ] ) ) { + return; + } + + this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); + + // Support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + this._addClass( tab, "ui-tabs-loading" ); + panel.attr( "aria-busy", "true" ); + + this.xhr + .done( function( response, status, jqXHR ) { + + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout( function() { + panel.html( response ); + that._trigger( "load", event, eventData ); + + complete( jqXHR, status ); + }, 1 ); + } ) + .fail( function( jqXHR, status ) { + + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout( function() { + complete( jqXHR, status ); + }, 1 ); + } ); + } + }, + + _ajaxSettings: function( anchor, event, eventData ) { + var that = this; + return { + + // Support: IE <11 only + // Strip any hash that exists to prevent errors with the Ajax request + url: anchor.attr( "href" ).replace( /#.*$/, "" ), + beforeSend: function( jqXHR, settings ) { + return that._trigger( "beforeLoad", event, + $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) ); + } + }; + }, + + _getPanelForTab: function( tab ) { + var id = $( tab ).attr( "aria-controls" ); + return this.element.find( this._sanitizeSelector( "#" + id ) ); + } + } ); // DEPRECATED // TODO: Switch return back to widget declaration at top of file when this is removed -if ( $.uiBackCompat !== false ) { + if ( $.uiBackCompat !== false ) { - // Backcompat for ui-tab class (now ui-tabs-tab) - $.widget( "ui.tabs", $.ui.tabs, { - _processTabs: function() { - this._superApply( arguments ); - this._addClass( this.tabs, "ui-tab" ); - } - } ); -} + // Backcompat for ui-tab class (now ui-tabs-tab) + $.widget( "ui.tabs", $.ui.tabs, { + _processTabs: function() { + this._superApply( arguments ); + this._addClass( this.tabs, "ui-tab" ); + } + } ); + } -var widgetsTabs = $.ui.tabs; + var widgetsTabs = $.ui.tabs; -/*! - * jQuery UI Tooltip 1.12.1 + /*! + * jQuery UI Tooltip 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -15643,2117 +15949,2164 @@ var widgetsTabs = $.ui.tabs; //>>css.theme: ../../themes/base/theme.css - -$.widget( "ui.tooltip", { - version: "1.12.1", - options: { - classes: { - "ui-tooltip": "ui-corner-all ui-widget-shadow" - }, - content: function() { - - // support: IE<9, Opera in jQuery <1.7 - // .text() can't accept undefined, so coerce to a string - var title = $( this ).attr( "title" ) || ""; - - // Escape title, since we're going from an attribute to raw HTML - return $( "<a>" ).text( title ).html(); - }, - hide: true, - - // Disabled elements have inconsistent behavior across browsers (#8661) - items: "[title]:not([disabled])", - position: { - my: "left top+15", - at: "left bottom", - collision: "flipfit flip" - }, - show: true, - track: false, - - // Callbacks - close: null, - open: null - }, - - _addDescribedBy: function( elem, id ) { - var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ); - describedby.push( id ); - elem - .data( "ui-tooltip-id", id ) - .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); - }, - - _removeDescribedBy: function( elem ) { - var id = elem.data( "ui-tooltip-id" ), - describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ), - index = $.inArray( id, describedby ); - - if ( index !== -1 ) { - describedby.splice( index, 1 ); - } - - elem.removeData( "ui-tooltip-id" ); - describedby = $.trim( describedby.join( " " ) ); - if ( describedby ) { - elem.attr( "aria-describedby", describedby ); - } else { - elem.removeAttr( "aria-describedby" ); - } - }, - - _create: function() { - this._on( { - mouseover: "open", - focusin: "open" - } ); - - // IDs of generated tooltips, needed for destroy - this.tooltips = {}; - - // IDs of parent tooltips where we removed the title attribute - this.parents = {}; - - // Append the aria-live region so tooltips announce correctly - this.liveRegion = $( "<div>" ) - .attr( { - role: "log", - "aria-live": "assertive", - "aria-relevant": "additions" - } ) - .appendTo( this.document[ 0 ].body ); - this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); - - this.disabledTitles = $( [] ); - }, - - _setOption: function( key, value ) { - var that = this; - - this._super( key, value ); - - if ( key === "content" ) { - $.each( this.tooltips, function( id, tooltipData ) { - that._updateContent( tooltipData.element ); - } ); - } - }, - - _setOptionDisabled: function( value ) { - this[ value ? "_disable" : "_enable" ](); - }, - - _disable: function() { - var that = this; - - // Close open tooltips - $.each( this.tooltips, function( id, tooltipData ) { - var event = $.Event( "blur" ); - event.target = event.currentTarget = tooltipData.element[ 0 ]; - that.close( event, true ); - } ); - - // Remove title attributes to prevent native tooltips - this.disabledTitles = this.disabledTitles.add( - this.element.find( this.options.items ).addBack() - .filter( function() { - var element = $( this ); - if ( element.is( "[title]" ) ) { - return element - .data( "ui-tooltip-title", element.attr( "title" ) ) - .removeAttr( "title" ); - } - } ) - ); - }, - - _enable: function() { - - // restore title attributes - this.disabledTitles.each( function() { - var element = $( this ); - if ( element.data( "ui-tooltip-title" ) ) { - element.attr( "title", element.data( "ui-tooltip-title" ) ); - } - } ); - this.disabledTitles = $( [] ); - }, - - open: function( event ) { - var that = this, - target = $( event ? event.target : this.element ) - - // we need closest here due to mouseover bubbling, - // but always pointing at the same event target - .closest( this.options.items ); - - // No element to show a tooltip for or the tooltip is already open - if ( !target.length || target.data( "ui-tooltip-id" ) ) { - return; - } - - if ( target.attr( "title" ) ) { - target.data( "ui-tooltip-title", target.attr( "title" ) ); - } - - target.data( "ui-tooltip-open", true ); - - // Kill parent tooltips, custom or native, for hover - if ( event && event.type === "mouseover" ) { - target.parents().each( function() { - var parent = $( this ), - blurEvent; - if ( parent.data( "ui-tooltip-open" ) ) { - blurEvent = $.Event( "blur" ); - blurEvent.target = blurEvent.currentTarget = this; - that.close( blurEvent, true ); - } - if ( parent.attr( "title" ) ) { - parent.uniqueId(); - that.parents[ this.id ] = { - element: this, - title: parent.attr( "title" ) - }; - parent.attr( "title", "" ); - } - } ); - } - - this._registerCloseHandlers( event, target ); - this._updateContent( target, event ); - }, - - _updateContent: function( target, event ) { - var content, - contentOption = this.options.content, - that = this, - eventType = event ? event.type : null; - - if ( typeof contentOption === "string" || contentOption.nodeType || - contentOption.jquery ) { - return this._open( event, target, contentOption ); - } - - content = contentOption.call( target[ 0 ], function( response ) { - - // IE may instantly serve a cached response for ajax requests - // delay this call to _open so the other call to _open runs first - that._delay( function() { - - // Ignore async response if tooltip was closed already - if ( !target.data( "ui-tooltip-open" ) ) { - return; - } - - // JQuery creates a special event for focusin when it doesn't - // exist natively. To improve performance, the native event - // object is reused and the type is changed. Therefore, we can't - // rely on the type being correct after the event finished - // bubbling, so we set it back to the previous value. (#8740) - if ( event ) { - event.type = eventType; - } - this._open( event, target, response ); - } ); - } ); - if ( content ) { - this._open( event, target, content ); - } - }, - - _open: function( event, target, content ) { - var tooltipData, tooltip, delayedShow, a11yContent, - positionOption = $.extend( {}, this.options.position ); - - if ( !content ) { - return; - } - - // Content can be updated multiple times. If the tooltip already - // exists, then just update the content and bail. - tooltipData = this._find( target ); - if ( tooltipData ) { - tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content ); - return; - } - - // If we have a title, clear it to prevent the native tooltip - // we have to check first to avoid defining a title if none exists - // (we don't want to cause an element to start matching [title]) - // - // We use removeAttr only for key events, to allow IE to export the correct - // accessible attributes. For mouse events, set to empty string to avoid - // native tooltip showing up (happens only when removing inside mouseover). - if ( target.is( "[title]" ) ) { - if ( event && event.type === "mouseover" ) { - target.attr( "title", "" ); - } else { - target.removeAttr( "title" ); - } - } - - tooltipData = this._tooltip( target ); - tooltip = tooltipData.tooltip; - this._addDescribedBy( target, tooltip.attr( "id" ) ); - tooltip.find( ".ui-tooltip-content" ).html( content ); - - // Support: Voiceover on OS X, JAWS on IE <= 9 - // JAWS announces deletions even when aria-relevant="additions" - // Voiceover will sometimes re-read the entire log region's contents from the beginning - this.liveRegion.children().hide(); - a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() ); - a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" ); - a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" ); - a11yContent.appendTo( this.liveRegion ); - - function position( event ) { - positionOption.of = event; - if ( tooltip.is( ":hidden" ) ) { - return; - } - tooltip.position( positionOption ); - } - if ( this.options.track && event && /^mouse/.test( event.type ) ) { - this._on( this.document, { - mousemove: position - } ); - - // trigger once to override element-relative positioning - position( event ); - } else { - tooltip.position( $.extend( { - of: target - }, this.options.position ) ); - } - - tooltip.hide(); - - this._show( tooltip, this.options.show ); - - // Handle tracking tooltips that are shown with a delay (#8644). As soon - // as the tooltip is visible, position the tooltip using the most recent - // event. - // Adds the check to add the timers only when both delay and track options are set (#14682) - if ( this.options.track && this.options.show && this.options.show.delay ) { - delayedShow = this.delayedShow = setInterval( function() { - if ( tooltip.is( ":visible" ) ) { - position( positionOption.of ); - clearInterval( delayedShow ); - } - }, $.fx.interval ); - } - - this._trigger( "open", event, { tooltip: tooltip } ); - }, - - _registerCloseHandlers: function( event, target ) { - var events = { - keyup: function( event ) { - if ( event.keyCode === $.ui.keyCode.ESCAPE ) { - var fakeEvent = $.Event( event ); - fakeEvent.currentTarget = target[ 0 ]; - this.close( fakeEvent, true ); - } - } - }; - - // Only bind remove handler for delegated targets. Non-delegated - // tooltips will handle this in destroy. - if ( target[ 0 ] !== this.element[ 0 ] ) { - events.remove = function() { - this._removeTooltip( this._find( target ).tooltip ); - }; - } - - if ( !event || event.type === "mouseover" ) { - events.mouseleave = "close"; - } - if ( !event || event.type === "focusin" ) { - events.focusout = "close"; - } - this._on( true, target, events ); - }, - - close: function( event ) { - var tooltip, - that = this, - target = $( event ? event.currentTarget : this.element ), - tooltipData = this._find( target ); - - // The tooltip may already be closed - if ( !tooltipData ) { - - // We set ui-tooltip-open immediately upon open (in open()), but only set the - // additional data once there's actually content to show (in _open()). So even if the - // tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in - // the period between open() and _open(). - target.removeData( "ui-tooltip-open" ); - return; - } - - tooltip = tooltipData.tooltip; - - // Disabling closes the tooltip, so we need to track when we're closing - // to avoid an infinite loop in case the tooltip becomes disabled on close - if ( tooltipData.closing ) { - return; - } - - // Clear the interval for delayed tracking tooltips - clearInterval( this.delayedShow ); - - // Only set title if we had one before (see comment in _open()) - // If the title attribute has changed since open(), don't restore - if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) { - target.attr( "title", target.data( "ui-tooltip-title" ) ); - } - - this._removeDescribedBy( target ); - - tooltipData.hiding = true; - tooltip.stop( true ); - this._hide( tooltip, this.options.hide, function() { - that._removeTooltip( $( this ) ); - } ); - - target.removeData( "ui-tooltip-open" ); - this._off( target, "mouseleave focusout keyup" ); - - // Remove 'remove' binding only on delegated targets - if ( target[ 0 ] !== this.element[ 0 ] ) { - this._off( target, "remove" ); - } - this._off( this.document, "mousemove" ); - - if ( event && event.type === "mouseleave" ) { - $.each( this.parents, function( id, parent ) { - $( parent.element ).attr( "title", parent.title ); - delete that.parents[ id ]; - } ); - } - - tooltipData.closing = true; - this._trigger( "close", event, { tooltip: tooltip } ); - if ( !tooltipData.hiding ) { - tooltipData.closing = false; - } - }, - - _tooltip: function( element ) { - var tooltip = $( "<div>" ).attr( "role", "tooltip" ), - content = $( "<div>" ).appendTo( tooltip ), - id = tooltip.uniqueId().attr( "id" ); - - this._addClass( content, "ui-tooltip-content" ); - this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" ); - - tooltip.appendTo( this._appendTo( element ) ); - - return this.tooltips[ id ] = { - element: element, - tooltip: tooltip - }; - }, - - _find: function( target ) { - var id = target.data( "ui-tooltip-id" ); - return id ? this.tooltips[ id ] : null; - }, - - _removeTooltip: function( tooltip ) { - tooltip.remove(); - delete this.tooltips[ tooltip.attr( "id" ) ]; - }, - - _appendTo: function( target ) { - var element = target.closest( ".ui-front, dialog" ); - - if ( !element.length ) { - element = this.document[ 0 ].body; - } - - return element; - }, - - _destroy: function() { - var that = this; - - // Close open tooltips - $.each( this.tooltips, function( id, tooltipData ) { - - // Delegate to close method to handle common cleanup - var event = $.Event( "blur" ), - element = tooltipData.element; - event.target = event.currentTarget = element[ 0 ]; - that.close( event, true ); - - // Remove immediately; destroying an open tooltip doesn't use the - // hide animation - $( "#" + id ).remove(); - - // Restore the title - if ( element.data( "ui-tooltip-title" ) ) { - - // If the title attribute has changed since open(), don't restore - if ( !element.attr( "title" ) ) { - element.attr( "title", element.data( "ui-tooltip-title" ) ); - } - element.removeData( "ui-tooltip-title" ); - } - } ); - this.liveRegion.remove(); - } -} ); + $.widget( "ui.tooltip", { + version: "1.13.0", + options: { + classes: { + "ui-tooltip": "ui-corner-all ui-widget-shadow" + }, + content: function() { + var title = $( this ).attr( "title" ); + + // Escape title, since we're going from an attribute to raw HTML + return $( "<a>" ).text( title ).html(); + }, + hide: true, + + // Disabled elements have inconsistent behavior across browsers (#8661) + items: "[title]:not([disabled])", + position: { + my: "left top+15", + at: "left bottom", + collision: "flipfit flip" + }, + show: true, + track: false, + + // Callbacks + close: null, + open: null + }, + + _addDescribedBy: function( elem, id ) { + var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ); + describedby.push( id ); + elem + .data( "ui-tooltip-id", id ) + .attr( "aria-describedby", String.prototype.trim.call( describedby.join( " " ) ) ); + }, + + _removeDescribedBy: function( elem ) { + var id = elem.data( "ui-tooltip-id" ), + describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ), + index = $.inArray( id, describedby ); + + if ( index !== -1 ) { + describedby.splice( index, 1 ); + } + + elem.removeData( "ui-tooltip-id" ); + describedby = String.prototype.trim.call( describedby.join( " " ) ); + if ( describedby ) { + elem.attr( "aria-describedby", describedby ); + } else { + elem.removeAttr( "aria-describedby" ); + } + }, + + _create: function() { + this._on( { + mouseover: "open", + focusin: "open" + } ); + + // IDs of generated tooltips, needed for destroy + this.tooltips = {}; + + // IDs of parent tooltips where we removed the title attribute + this.parents = {}; + + // Append the aria-live region so tooltips announce correctly + this.liveRegion = $( "<div>" ) + .attr( { + role: "log", + "aria-live": "assertive", + "aria-relevant": "additions" + } ) + .appendTo( this.document[ 0 ].body ); + this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); + + this.disabledTitles = $( [] ); + }, + + _setOption: function( key, value ) { + var that = this; + + this._super( key, value ); + + if ( key === "content" ) { + $.each( this.tooltips, function( id, tooltipData ) { + that._updateContent( tooltipData.element ); + } ); + } + }, + + _setOptionDisabled: function( value ) { + this[ value ? "_disable" : "_enable" ](); + }, + + _disable: function() { + var that = this; + + // Close open tooltips + $.each( this.tooltips, function( id, tooltipData ) { + var event = $.Event( "blur" ); + event.target = event.currentTarget = tooltipData.element[ 0 ]; + that.close( event, true ); + } ); + + // Remove title attributes to prevent native tooltips + this.disabledTitles = this.disabledTitles.add( + this.element.find( this.options.items ).addBack() + .filter( function() { + var element = $( this ); + if ( element.is( "[title]" ) ) { + return element + .data( "ui-tooltip-title", element.attr( "title" ) ) + .removeAttr( "title" ); + } + } ) + ); + }, + + _enable: function() { + + // restore title attributes + this.disabledTitles.each( function() { + var element = $( this ); + if ( element.data( "ui-tooltip-title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + } + } ); + this.disabledTitles = $( [] ); + }, + + open: function( event ) { + var that = this, + target = $( event ? event.target : this.element ) + + // we need closest here due to mouseover bubbling, + // but always pointing at the same event target + .closest( this.options.items ); + + // No element to show a tooltip for or the tooltip is already open + if ( !target.length || target.data( "ui-tooltip-id" ) ) { + return; + } + + if ( target.attr( "title" ) ) { + target.data( "ui-tooltip-title", target.attr( "title" ) ); + } + + target.data( "ui-tooltip-open", true ); + + // Kill parent tooltips, custom or native, for hover + if ( event && event.type === "mouseover" ) { + target.parents().each( function() { + var parent = $( this ), + blurEvent; + if ( parent.data( "ui-tooltip-open" ) ) { + blurEvent = $.Event( "blur" ); + blurEvent.target = blurEvent.currentTarget = this; + that.close( blurEvent, true ); + } + if ( parent.attr( "title" ) ) { + parent.uniqueId(); + that.parents[ this.id ] = { + element: this, + title: parent.attr( "title" ) + }; + parent.attr( "title", "" ); + } + } ); + } + + this._registerCloseHandlers( event, target ); + this._updateContent( target, event ); + }, + + _updateContent: function( target, event ) { + var content, + contentOption = this.options.content, + that = this, + eventType = event ? event.type : null; + + if ( typeof contentOption === "string" || contentOption.nodeType || + contentOption.jquery ) { + return this._open( event, target, contentOption ); + } + + content = contentOption.call( target[ 0 ], function( response ) { + + // IE may instantly serve a cached response for ajax requests + // delay this call to _open so the other call to _open runs first + that._delay( function() { + + // Ignore async response if tooltip was closed already + if ( !target.data( "ui-tooltip-open" ) ) { + return; + } + + // JQuery creates a special event for focusin when it doesn't + // exist natively. To improve performance, the native event + // object is reused and the type is changed. Therefore, we can't + // rely on the type being correct after the event finished + // bubbling, so we set it back to the previous value. (#8740) + if ( event ) { + event.type = eventType; + } + this._open( event, target, response ); + } ); + } ); + if ( content ) { + this._open( event, target, content ); + } + }, + + _open: function( event, target, content ) { + var tooltipData, tooltip, delayedShow, a11yContent, + positionOption = $.extend( {}, this.options.position ); + + if ( !content ) { + return; + } + + // Content can be updated multiple times. If the tooltip already + // exists, then just update the content and bail. + tooltipData = this._find( target ); + if ( tooltipData ) { + tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content ); + return; + } + + // If we have a title, clear it to prevent the native tooltip + // we have to check first to avoid defining a title if none exists + // (we don't want to cause an element to start matching [title]) + // + // We use removeAttr only for key events, to allow IE to export the correct + // accessible attributes. For mouse events, set to empty string to avoid + // native tooltip showing up (happens only when removing inside mouseover). + if ( target.is( "[title]" ) ) { + if ( event && event.type === "mouseover" ) { + target.attr( "title", "" ); + } else { + target.removeAttr( "title" ); + } + } + + tooltipData = this._tooltip( target ); + tooltip = tooltipData.tooltip; + this._addDescribedBy( target, tooltip.attr( "id" ) ); + tooltip.find( ".ui-tooltip-content" ).html( content ); + + // Support: Voiceover on OS X, JAWS on IE <= 9 + // JAWS announces deletions even when aria-relevant="additions" + // Voiceover will sometimes re-read the entire log region's contents from the beginning + this.liveRegion.children().hide(); + a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() ); + a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" ); + a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" ); + a11yContent.appendTo( this.liveRegion ); + + function position( event ) { + positionOption.of = event; + if ( tooltip.is( ":hidden" ) ) { + return; + } + tooltip.position( positionOption ); + } + if ( this.options.track && event && /^mouse/.test( event.type ) ) { + this._on( this.document, { + mousemove: position + } ); + + // trigger once to override element-relative positioning + position( event ); + } else { + tooltip.position( $.extend( { + of: target + }, this.options.position ) ); + } + + tooltip.hide(); + + this._show( tooltip, this.options.show ); + + // Handle tracking tooltips that are shown with a delay (#8644). As soon + // as the tooltip is visible, position the tooltip using the most recent + // event. + // Adds the check to add the timers only when both delay and track options are set (#14682) + if ( this.options.track && this.options.show && this.options.show.delay ) { + delayedShow = this.delayedShow = setInterval( function() { + if ( tooltip.is( ":visible" ) ) { + position( positionOption.of ); + clearInterval( delayedShow ); + } + }, 13 ); + } + + this._trigger( "open", event, { tooltip: tooltip } ); + }, + + _registerCloseHandlers: function( event, target ) { + var events = { + keyup: function( event ) { + if ( event.keyCode === $.ui.keyCode.ESCAPE ) { + var fakeEvent = $.Event( event ); + fakeEvent.currentTarget = target[ 0 ]; + this.close( fakeEvent, true ); + } + } + }; + + // Only bind remove handler for delegated targets. Non-delegated + // tooltips will handle this in destroy. + if ( target[ 0 ] !== this.element[ 0 ] ) { + events.remove = function() { + this._removeTooltip( this._find( target ).tooltip ); + }; + } + + if ( !event || event.type === "mouseover" ) { + events.mouseleave = "close"; + } + if ( !event || event.type === "focusin" ) { + events.focusout = "close"; + } + this._on( true, target, events ); + }, + + close: function( event ) { + var tooltip, + that = this, + target = $( event ? event.currentTarget : this.element ), + tooltipData = this._find( target ); + + // The tooltip may already be closed + if ( !tooltipData ) { + + // We set ui-tooltip-open immediately upon open (in open()), but only set the + // additional data once there's actually content to show (in _open()). So even if the + // tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in + // the period between open() and _open(). + target.removeData( "ui-tooltip-open" ); + return; + } + + tooltip = tooltipData.tooltip; + + // Disabling closes the tooltip, so we need to track when we're closing + // to avoid an infinite loop in case the tooltip becomes disabled on close + if ( tooltipData.closing ) { + return; + } + + // Clear the interval for delayed tracking tooltips + clearInterval( this.delayedShow ); + + // Only set title if we had one before (see comment in _open()) + // If the title attribute has changed since open(), don't restore + if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) { + target.attr( "title", target.data( "ui-tooltip-title" ) ); + } + + this._removeDescribedBy( target ); + + tooltipData.hiding = true; + tooltip.stop( true ); + this._hide( tooltip, this.options.hide, function() { + that._removeTooltip( $( this ) ); + } ); + + target.removeData( "ui-tooltip-open" ); + this._off( target, "mouseleave focusout keyup" ); + + // Remove 'remove' binding only on delegated targets + if ( target[ 0 ] !== this.element[ 0 ] ) { + this._off( target, "remove" ); + } + this._off( this.document, "mousemove" ); + + if ( event && event.type === "mouseleave" ) { + $.each( this.parents, function( id, parent ) { + $( parent.element ).attr( "title", parent.title ); + delete that.parents[ id ]; + } ); + } + + tooltipData.closing = true; + this._trigger( "close", event, { tooltip: tooltip } ); + if ( !tooltipData.hiding ) { + tooltipData.closing = false; + } + }, + + _tooltip: function( element ) { + var tooltip = $( "<div>" ).attr( "role", "tooltip" ), + content = $( "<div>" ).appendTo( tooltip ), + id = tooltip.uniqueId().attr( "id" ); + + this._addClass( content, "ui-tooltip-content" ); + this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" ); + + tooltip.appendTo( this._appendTo( element ) ); + + return this.tooltips[ id ] = { + element: element, + tooltip: tooltip + }; + }, + + _find: function( target ) { + var id = target.data( "ui-tooltip-id" ); + return id ? this.tooltips[ id ] : null; + }, + + _removeTooltip: function( tooltip ) { + + // Clear the interval for delayed tracking tooltips + clearInterval( this.delayedShow ); + + tooltip.remove(); + delete this.tooltips[ tooltip.attr( "id" ) ]; + }, + + _appendTo: function( target ) { + var element = target.closest( ".ui-front, dialog" ); + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _destroy: function() { + var that = this; + + // Close open tooltips + $.each( this.tooltips, function( id, tooltipData ) { + + // Delegate to close method to handle common cleanup + var event = $.Event( "blur" ), + element = tooltipData.element; + event.target = event.currentTarget = element[ 0 ]; + that.close( event, true ); + + // Remove immediately; destroying an open tooltip doesn't use the + // hide animation + $( "#" + id ).remove(); + + // Restore the title + if ( element.data( "ui-tooltip-title" ) ) { + + // If the title attribute has changed since open(), don't restore + if ( !element.attr( "title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + } + element.removeData( "ui-tooltip-title" ); + } + } ); + this.liveRegion.remove(); + } + } ); // DEPRECATED // TODO: Switch return back to widget declaration at top of file when this is removed -if ( $.uiBackCompat !== false ) { - - // Backcompat for tooltipClass option - $.widget( "ui.tooltip", $.ui.tooltip, { - options: { - tooltipClass: null - }, - _tooltip: function() { - var tooltipData = this._superApply( arguments ); - if ( this.options.tooltipClass ) { - tooltipData.tooltip.addClass( this.options.tooltipClass ); - } - return tooltipData; - } - } ); -} - -var widgetsTooltip = $.ui.tooltip; - - -/*! - * jQuery UI Effects 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ + if ( $.uiBackCompat !== false ) { -//>>label: Effects Core -//>>group: Effects -// jscs:disable maximumLineLength -//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects. -// jscs:enable maximumLineLength -//>>docs: http://api.jqueryui.com/category/effects-core/ -//>>demos: http://jqueryui.com/effect/ + // Backcompat for tooltipClass option + $.widget( "ui.tooltip", $.ui.tooltip, { + options: { + tooltipClass: null + }, + _tooltip: function() { + var tooltipData = this._superApply( arguments ); + if ( this.options.tooltipClass ) { + tooltipData.tooltip.addClass( this.options.tooltipClass ); + } + return tooltipData; + } + } ); + } + var widgetsTooltip = $.ui.tooltip; -var dataSpace = "ui-effects-", - dataSpaceStyle = "ui-effects-style", - dataSpaceAnimated = "ui-effects-animated", - // Create a local jQuery because jQuery Color relies on it and the - // global may not exist with AMD and a custom build (#10199) - jQuery = $; +// Create a local jQuery because jQuery Color relies on it and the +// global may not exist with AMD and a custom build (#10199). +// This module is a noop if used as a regular AMD module. +// eslint-disable-next-line no-unused-vars + var jQuery = $; -$.effects = { - effect: {} -}; -/*! - * jQuery Color Animations v2.1.2 + /*! + * jQuery Color Animations v2.2.0 * https://github.com/jquery/jquery-color * - * Copyright 2014 jQuery Foundation and other contributors + * Copyright OpenJS Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * - * Date: Wed Jan 16 08:47:09 2013 -0600 + * Date: Sun May 10 09:02:36 2020 +0200 */ -( function( jQuery, undefined ) { - - var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + - "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", - - // Plusequals test for += 100 -= 100 - rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, - - // A set of RE's that can match strings and generate color tuples. - stringParsers = [ { - re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - parse: function( execResult ) { - return [ - execResult[ 1 ], - execResult[ 2 ], - execResult[ 3 ], - execResult[ 4 ] - ]; - } - }, { - re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - parse: function( execResult ) { - return [ - execResult[ 1 ] * 2.55, - execResult[ 2 ] * 2.55, - execResult[ 3 ] * 2.55, - execResult[ 4 ] - ]; - } - }, { - - // This regex ignores A-F because it's compared against an already lowercased string - re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, - parse: function( execResult ) { - return [ - parseInt( execResult[ 1 ], 16 ), - parseInt( execResult[ 2 ], 16 ), - parseInt( execResult[ 3 ], 16 ) - ]; - } - }, { - - // This regex ignores A-F because it's compared against an already lowercased string - re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, - parse: function( execResult ) { - return [ - parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), - parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), - parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) - ]; - } - }, { - re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - space: "hsla", - parse: function( execResult ) { - return [ - execResult[ 1 ], - execResult[ 2 ] / 100, - execResult[ 3 ] / 100, - execResult[ 4 ] - ]; - } - } ], - - // JQuery.Color( ) - color = jQuery.Color = function( color, green, blue, alpha ) { - return new jQuery.Color.fn.parse( color, green, blue, alpha ); - }, - spaces = { - rgba: { - props: { - red: { - idx: 0, - type: "byte" - }, - green: { - idx: 1, - type: "byte" - }, - blue: { - idx: 2, - type: "byte" - } - } - }, - - hsla: { - props: { - hue: { - idx: 0, - type: "degrees" - }, - saturation: { - idx: 1, - type: "percent" - }, - lightness: { - idx: 2, - type: "percent" - } - } - } - }, - propTypes = { - "byte": { - floor: true, - max: 255 - }, - "percent": { - max: 1 - }, - "degrees": { - mod: 360, - floor: true - } - }, - support = color.support = {}, - - // Element for support tests - supportElem = jQuery( "<p>" )[ 0 ], - - // Colors = jQuery.Color.names - colors, - - // Local aliases of functions called often - each = jQuery.each; - -// Determine rgba support immediately -supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; -support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; - -// Define cache name and alpha properties -// for rgba and hsla spaces -each( spaces, function( spaceName, space ) { - space.cache = "_" + spaceName; - space.props.alpha = { - idx: 3, - type: "percent", - def: 1 - }; -} ); -function clamp( value, prop, allowEmpty ) { - var type = propTypes[ prop.type ] || {}; - - if ( value == null ) { - return ( allowEmpty || !prop.def ) ? null : prop.def; - } - - // ~~ is an short way of doing floor for positive numbers - value = type.floor ? ~~value : parseFloat( value ); - - // IE will pass in empty strings as value for alpha, - // which will hit this case - if ( isNaN( value ) ) { - return prop.def; - } - - if ( type.mod ) { - - // We add mod before modding to make sure that negatives values - // get converted properly: -10 -> 350 - return ( value + type.mod ) % type.mod; - } - - // For now all property types without mod have min and max - return 0 > value ? 0 : type.max < value ? type.max : value; -} - -function stringParse( string ) { - var inst = color(), - rgba = inst._rgba = []; - - string = string.toLowerCase(); - - each( stringParsers, function( i, parser ) { - var parsed, - match = parser.re.exec( string ), - values = match && parser.parse( match ), - spaceName = parser.space || "rgba"; - - if ( values ) { - parsed = inst[ spaceName ]( values ); - - // If this was an rgba parse the assignment might happen twice - // oh well.... - inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; - rgba = inst._rgba = parsed._rgba; - - // Exit each( stringParsers ) here because we matched - return false; - } - } ); - - // Found a stringParser that handled it - if ( rgba.length ) { - - // If this came from a parsed string, force "transparent" when alpha is 0 - // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) - if ( rgba.join() === "0,0,0,0" ) { - jQuery.extend( rgba, colors.transparent ); - } - return inst; - } - - // Named colors - return colors[ string ]; -} - -color.fn = jQuery.extend( color.prototype, { - parse: function( red, green, blue, alpha ) { - if ( red === undefined ) { - this._rgba = [ null, null, null, null ]; - return this; - } - if ( red.jquery || red.nodeType ) { - red = jQuery( red ).css( green ); - green = undefined; - } - - var inst = this, - type = jQuery.type( red ), - rgba = this._rgba = []; - - // More than 1 argument specified - assume ( red, green, blue, alpha ) - if ( green !== undefined ) { - red = [ red, green, blue, alpha ]; - type = "array"; - } - - if ( type === "string" ) { - return this.parse( stringParse( red ) || colors._default ); - } - - if ( type === "array" ) { - each( spaces.rgba.props, function( key, prop ) { - rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); - } ); - return this; - } - - if ( type === "object" ) { - if ( red instanceof color ) { - each( spaces, function( spaceName, space ) { - if ( red[ space.cache ] ) { - inst[ space.cache ] = red[ space.cache ].slice(); - } - } ); - } else { - each( spaces, function( spaceName, space ) { - var cache = space.cache; - each( space.props, function( key, prop ) { - - // If the cache doesn't exist, and we know how to convert - if ( !inst[ cache ] && space.to ) { - - // If the value was null, we don't need to copy it - // if the key was alpha, we don't need to copy it either - if ( key === "alpha" || red[ key ] == null ) { - return; - } - inst[ cache ] = space.to( inst._rgba ); - } - - // This is the only case where we allow nulls for ALL properties. - // call clamp with alwaysAllowEmpty - inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); - } ); - - // Everything defined but alpha? - if ( inst[ cache ] && - jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { - - // Use the default of 1 - inst[ cache ][ 3 ] = 1; - if ( space.from ) { - inst._rgba = space.from( inst[ cache ] ); - } - } - } ); - } - return this; - } - }, - is: function( compare ) { - var is = color( compare ), - same = true, - inst = this; - - each( spaces, function( _, space ) { - var localCache, - isCache = is[ space.cache ]; - if ( isCache ) { - localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; - each( space.props, function( _, prop ) { - if ( isCache[ prop.idx ] != null ) { - same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); - return same; - } - } ); - } - return same; - } ); - return same; - }, - _space: function() { - var used = [], - inst = this; - each( spaces, function( spaceName, space ) { - if ( inst[ space.cache ] ) { - used.push( spaceName ); - } - } ); - return used.pop(); - }, - transition: function( other, distance ) { - var end = color( other ), - spaceName = end._space(), - space = spaces[ spaceName ], - startColor = this.alpha() === 0 ? color( "transparent" ) : this, - start = startColor[ space.cache ] || space.to( startColor._rgba ), - result = start.slice(); - - end = end[ space.cache ]; - each( space.props, function( key, prop ) { - var index = prop.idx, - startValue = start[ index ], - endValue = end[ index ], - type = propTypes[ prop.type ] || {}; - - // If null, don't override start value - if ( endValue === null ) { - return; - } - - // If null - use end - if ( startValue === null ) { - result[ index ] = endValue; - } else { - if ( type.mod ) { - if ( endValue - startValue > type.mod / 2 ) { - startValue += type.mod; - } else if ( startValue - endValue > type.mod / 2 ) { - startValue -= type.mod; - } - } - result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); - } - } ); - return this[ spaceName ]( result ); - }, - blend: function( opaque ) { - - // If we are already opaque - return ourself - if ( this._rgba[ 3 ] === 1 ) { - return this; - } - - var rgb = this._rgba.slice(), - a = rgb.pop(), - blend = color( opaque )._rgba; - - return color( jQuery.map( rgb, function( v, i ) { - return ( 1 - a ) * blend[ i ] + a * v; - } ) ); - }, - toRgbaString: function() { - var prefix = "rgba(", - rgba = jQuery.map( this._rgba, function( v, i ) { - return v == null ? ( i > 2 ? 1 : 0 ) : v; - } ); - - if ( rgba[ 3 ] === 1 ) { - rgba.pop(); - prefix = "rgb("; - } - - return prefix + rgba.join() + ")"; - }, - toHslaString: function() { - var prefix = "hsla(", - hsla = jQuery.map( this.hsla(), function( v, i ) { - if ( v == null ) { - v = i > 2 ? 1 : 0; - } - - // Catch 1 and 2 - if ( i && i < 3 ) { - v = Math.round( v * 100 ) + "%"; - } - return v; - } ); - - if ( hsla[ 3 ] === 1 ) { - hsla.pop(); - prefix = "hsl("; - } - return prefix + hsla.join() + ")"; - }, - toHexString: function( includeAlpha ) { - var rgba = this._rgba.slice(), - alpha = rgba.pop(); - - if ( includeAlpha ) { - rgba.push( ~~( alpha * 255 ) ); - } - - return "#" + jQuery.map( rgba, function( v ) { - - // Default to 0 when nulls exist - v = ( v || 0 ).toString( 16 ); - return v.length === 1 ? "0" + v : v; - } ).join( "" ); - }, - toString: function() { - return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); - } -} ); -color.fn.parse.prototype = color.fn; -// Hsla conversions adapted from: -// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 -function hue2rgb( p, q, h ) { - h = ( h + 1 ) % 1; - if ( h * 6 < 1 ) { - return p + ( q - p ) * h * 6; - } - if ( h * 2 < 1 ) { - return q; - } - if ( h * 3 < 2 ) { - return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; - } - return p; -} - -spaces.hsla.to = function( rgba ) { - if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { - return [ null, null, null, rgba[ 3 ] ]; - } - var r = rgba[ 0 ] / 255, - g = rgba[ 1 ] / 255, - b = rgba[ 2 ] / 255, - a = rgba[ 3 ], - max = Math.max( r, g, b ), - min = Math.min( r, g, b ), - diff = max - min, - add = max + min, - l = add * 0.5, - h, s; - - if ( min === max ) { - h = 0; - } else if ( r === max ) { - h = ( 60 * ( g - b ) / diff ) + 360; - } else if ( g === max ) { - h = ( 60 * ( b - r ) / diff ) + 120; - } else { - h = ( 60 * ( r - g ) / diff ) + 240; - } - - // Chroma (diff) == 0 means greyscale which, by definition, saturation = 0% - // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) - if ( diff === 0 ) { - s = 0; - } else if ( l <= 0.5 ) { - s = diff / add; - } else { - s = diff / ( 2 - add ); - } - return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; -}; - -spaces.hsla.from = function( hsla ) { - if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { - return [ null, null, null, hsla[ 3 ] ]; - } - var h = hsla[ 0 ] / 360, - s = hsla[ 1 ], - l = hsla[ 2 ], - a = hsla[ 3 ], - q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, - p = 2 * l - q; - - return [ - Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), - Math.round( hue2rgb( p, q, h ) * 255 ), - Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), - a - ]; -}; - -each( spaces, function( spaceName, space ) { - var props = space.props, - cache = space.cache, - to = space.to, - from = space.from; - - // Makes rgba() and hsla() - color.fn[ spaceName ] = function( value ) { - - // Generate a cache for this space if it doesn't exist - if ( to && !this[ cache ] ) { - this[ cache ] = to( this._rgba ); - } - if ( value === undefined ) { - return this[ cache ].slice(); - } - - var ret, - type = jQuery.type( value ), - arr = ( type === "array" || type === "object" ) ? value : arguments, - local = this[ cache ].slice(); - - each( props, function( key, prop ) { - var val = arr[ type === "object" ? key : prop.idx ]; - if ( val == null ) { - val = local[ prop.idx ]; - } - local[ prop.idx ] = clamp( val, prop ); - } ); - - if ( from ) { - ret = color( from( local ) ); - ret[ cache ] = local; - return ret; - } else { - return color( local ); - } - }; - - // Makes red() green() blue() alpha() hue() saturation() lightness() - each( props, function( key, prop ) { - - // Alpha is included in more than one space - if ( color.fn[ key ] ) { - return; - } - color.fn[ key ] = function( value ) { - var vtype = jQuery.type( value ), - fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), - local = this[ fn ](), - cur = local[ prop.idx ], - match; - - if ( vtype === "undefined" ) { - return cur; - } - - if ( vtype === "function" ) { - value = value.call( this, cur ); - vtype = jQuery.type( value ); - } - if ( value == null && prop.empty ) { - return this; - } - if ( vtype === "string" ) { - match = rplusequals.exec( value ); - if ( match ) { - value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); - } - } - local[ prop.idx ] = value; - return this[ fn ]( local ); - }; - } ); -} ); + var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + + "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", + + class2type = {}, + toString = class2type.toString, + + // plusequals test for += 100 -= 100 + rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, + + // a set of RE's that can match strings and generate color tuples. + stringParsers = [ { + re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ], + execResult[ 3 ], + execResult[ 4 ] + ]; + } + }, { + re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ] * 2.55, + execResult[ 2 ] * 2.55, + execResult[ 3 ] * 2.55, + execResult[ 4 ] + ]; + } + }, { + + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})?/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ], 16 ), + execResult[ 4 ] ? + ( parseInt( execResult[ 4 ], 16 ) / 255 ).toFixed( 2 ) : + 1 + ]; + } + }, { + + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])?/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ), + execResult[ 4 ] ? + ( parseInt( execResult[ 4 ] + execResult[ 4 ], 16 ) / 255 ) + .toFixed( 2 ) : + 1 + ]; + } + }, { + re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + space: "hsla", + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ] / 100, + execResult[ 3 ] / 100, + execResult[ 4 ] + ]; + } + } ], + + // jQuery.Color( ) + color = jQuery.Color = function( color, green, blue, alpha ) { + return new jQuery.Color.fn.parse( color, green, blue, alpha ); + }, + spaces = { + rgba: { + props: { + red: { + idx: 0, + type: "byte" + }, + green: { + idx: 1, + type: "byte" + }, + blue: { + idx: 2, + type: "byte" + } + } + }, + + hsla: { + props: { + hue: { + idx: 0, + type: "degrees" + }, + saturation: { + idx: 1, + type: "percent" + }, + lightness: { + idx: 2, + type: "percent" + } + } + } + }, + propTypes = { + "byte": { + floor: true, + max: 255 + }, + "percent": { + max: 1 + }, + "degrees": { + mod: 360, + floor: true + } + }, + support = color.support = {}, + + // element for support tests + supportElem = jQuery( "<p>" )[ 0 ], + + // colors = jQuery.Color.names + colors, + + // local aliases of functions called often + each = jQuery.each; + +// determine rgba support immediately + supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; + support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; + +// define cache name and alpha properties +// for rgba and hsla spaces + each( spaces, function( spaceName, space ) { + space.cache = "_" + spaceName; + space.props.alpha = { + idx: 3, + type: "percent", + def: 1 + }; + } ); + +// Populate the class2type map + jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + + function getType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + return typeof obj === "object" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + } + + function clamp( value, prop, allowEmpty ) { + var type = propTypes[ prop.type ] || {}; + + if ( value == null ) { + return ( allowEmpty || !prop.def ) ? null : prop.def; + } + + // ~~ is an short way of doing floor for positive numbers + value = type.floor ? ~~value : parseFloat( value ); + + // IE will pass in empty strings as value for alpha, + // which will hit this case + if ( isNaN( value ) ) { + return prop.def; + } + + if ( type.mod ) { + + // we add mod before modding to make sure that negatives values + // get converted properly: -10 -> 350 + return ( value + type.mod ) % type.mod; + } + + // for now all property types without mod have min and max + return Math.min( type.max, Math.max( 0, value ) ); + } + + function stringParse( string ) { + var inst = color(), + rgba = inst._rgba = []; + + string = string.toLowerCase(); + + each( stringParsers, function( _i, parser ) { + var parsed, + match = parser.re.exec( string ), + values = match && parser.parse( match ), + spaceName = parser.space || "rgba"; + + if ( values ) { + parsed = inst[ spaceName ]( values ); + + // if this was an rgba parse the assignment might happen twice + // oh well.... + inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; + rgba = inst._rgba = parsed._rgba; + + // exit each( stringParsers ) here because we matched + return false; + } + } ); + + // Found a stringParser that handled it + if ( rgba.length ) { + + // if this came from a parsed string, force "transparent" when alpha is 0 + // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) + if ( rgba.join() === "0,0,0,0" ) { + jQuery.extend( rgba, colors.transparent ); + } + return inst; + } + + // named colors + return colors[ string ]; + } + + color.fn = jQuery.extend( color.prototype, { + parse: function( red, green, blue, alpha ) { + if ( red === undefined ) { + this._rgba = [ null, null, null, null ]; + return this; + } + if ( red.jquery || red.nodeType ) { + red = jQuery( red ).css( green ); + green = undefined; + } + + var inst = this, + type = getType( red ), + rgba = this._rgba = []; + + // more than 1 argument specified - assume ( red, green, blue, alpha ) + if ( green !== undefined ) { + red = [ red, green, blue, alpha ]; + type = "array"; + } + + if ( type === "string" ) { + return this.parse( stringParse( red ) || colors._default ); + } + + if ( type === "array" ) { + each( spaces.rgba.props, function( _key, prop ) { + rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); + } ); + return this; + } + + if ( type === "object" ) { + if ( red instanceof color ) { + each( spaces, function( _spaceName, space ) { + if ( red[ space.cache ] ) { + inst[ space.cache ] = red[ space.cache ].slice(); + } + } ); + } else { + each( spaces, function( _spaceName, space ) { + var cache = space.cache; + each( space.props, function( key, prop ) { + + // if the cache doesn't exist, and we know how to convert + if ( !inst[ cache ] && space.to ) { + + // if the value was null, we don't need to copy it + // if the key was alpha, we don't need to copy it either + if ( key === "alpha" || red[ key ] == null ) { + return; + } + inst[ cache ] = space.to( inst._rgba ); + } + + // this is the only case where we allow nulls for ALL properties. + // call clamp with alwaysAllowEmpty + inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); + } ); + + // everything defined but alpha? + if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { + + // use the default of 1 + if ( inst[ cache ][ 3 ] == null ) { + inst[ cache ][ 3 ] = 1; + } + + if ( space.from ) { + inst._rgba = space.from( inst[ cache ] ); + } + } + } ); + } + return this; + } + }, + is: function( compare ) { + var is = color( compare ), + same = true, + inst = this; + + each( spaces, function( _, space ) { + var localCache, + isCache = is[ space.cache ]; + if ( isCache ) { + localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; + each( space.props, function( _, prop ) { + if ( isCache[ prop.idx ] != null ) { + same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); + return same; + } + } ); + } + return same; + } ); + return same; + }, + _space: function() { + var used = [], + inst = this; + each( spaces, function( spaceName, space ) { + if ( inst[ space.cache ] ) { + used.push( spaceName ); + } + } ); + return used.pop(); + }, + transition: function( other, distance ) { + var end = color( other ), + spaceName = end._space(), + space = spaces[ spaceName ], + startColor = this.alpha() === 0 ? color( "transparent" ) : this, + start = startColor[ space.cache ] || space.to( startColor._rgba ), + result = start.slice(); + + end = end[ space.cache ]; + each( space.props, function( _key, prop ) { + var index = prop.idx, + startValue = start[ index ], + endValue = end[ index ], + type = propTypes[ prop.type ] || {}; + + // if null, don't override start value + if ( endValue === null ) { + return; + } + + // if null - use end + if ( startValue === null ) { + result[ index ] = endValue; + } else { + if ( type.mod ) { + if ( endValue - startValue > type.mod / 2 ) { + startValue += type.mod; + } else if ( startValue - endValue > type.mod / 2 ) { + startValue -= type.mod; + } + } + result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); + } + } ); + return this[ spaceName ]( result ); + }, + blend: function( opaque ) { + + // if we are already opaque - return ourself + if ( this._rgba[ 3 ] === 1 ) { + return this; + } + + var rgb = this._rgba.slice(), + a = rgb.pop(), + blend = color( opaque )._rgba; + + return color( jQuery.map( rgb, function( v, i ) { + return ( 1 - a ) * blend[ i ] + a * v; + } ) ); + }, + toRgbaString: function() { + var prefix = "rgba(", + rgba = jQuery.map( this._rgba, function( v, i ) { + if ( v != null ) { + return v; + } + return i > 2 ? 1 : 0; + } ); + + if ( rgba[ 3 ] === 1 ) { + rgba.pop(); + prefix = "rgb("; + } + + return prefix + rgba.join() + ")"; + }, + toHslaString: function() { + var prefix = "hsla(", + hsla = jQuery.map( this.hsla(), function( v, i ) { + if ( v == null ) { + v = i > 2 ? 1 : 0; + } + + // catch 1 and 2 + if ( i && i < 3 ) { + v = Math.round( v * 100 ) + "%"; + } + return v; + } ); + + if ( hsla[ 3 ] === 1 ) { + hsla.pop(); + prefix = "hsl("; + } + return prefix + hsla.join() + ")"; + }, + toHexString: function( includeAlpha ) { + var rgba = this._rgba.slice(), + alpha = rgba.pop(); + + if ( includeAlpha ) { + rgba.push( ~~( alpha * 255 ) ); + } + + return "#" + jQuery.map( rgba, function( v ) { + + // default to 0 when nulls exist + v = ( v || 0 ).toString( 16 ); + return v.length === 1 ? "0" + v : v; + } ).join( "" ); + }, + toString: function() { + return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); + } + } ); + color.fn.parse.prototype = color.fn; + +// hsla conversions adapted from: +// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 -// Add cssHook and .fx.step function for each named hook. + function hue2rgb( p, q, h ) { + h = ( h + 1 ) % 1; + if ( h * 6 < 1 ) { + return p + ( q - p ) * h * 6; + } + if ( h * 2 < 1 ) { + return q; + } + if ( h * 3 < 2 ) { + return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; + } + return p; + } + + spaces.hsla.to = function( rgba ) { + if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { + return [ null, null, null, rgba[ 3 ] ]; + } + var r = rgba[ 0 ] / 255, + g = rgba[ 1 ] / 255, + b = rgba[ 2 ] / 255, + a = rgba[ 3 ], + max = Math.max( r, g, b ), + min = Math.min( r, g, b ), + diff = max - min, + add = max + min, + l = add * 0.5, + h, s; + + if ( min === max ) { + h = 0; + } else if ( r === max ) { + h = ( 60 * ( g - b ) / diff ) + 360; + } else if ( g === max ) { + h = ( 60 * ( b - r ) / diff ) + 120; + } else { + h = ( 60 * ( r - g ) / diff ) + 240; + } + + // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% + // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) + if ( diff === 0 ) { + s = 0; + } else if ( l <= 0.5 ) { + s = diff / add; + } else { + s = diff / ( 2 - add ); + } + return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; + }; + + spaces.hsla.from = function( hsla ) { + if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { + return [ null, null, null, hsla[ 3 ] ]; + } + var h = hsla[ 0 ] / 360, + s = hsla[ 1 ], + l = hsla[ 2 ], + a = hsla[ 3 ], + q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, + p = 2 * l - q; + + return [ + Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), + Math.round( hue2rgb( p, q, h ) * 255 ), + Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), + a + ]; + }; + + + each( spaces, function( spaceName, space ) { + var props = space.props, + cache = space.cache, + to = space.to, + from = space.from; + + // makes rgba() and hsla() + color.fn[ spaceName ] = function( value ) { + + // generate a cache for this space if it doesn't exist + if ( to && !this[ cache ] ) { + this[ cache ] = to( this._rgba ); + } + if ( value === undefined ) { + return this[ cache ].slice(); + } + + var ret, + type = getType( value ), + arr = ( type === "array" || type === "object" ) ? value : arguments, + local = this[ cache ].slice(); + + each( props, function( key, prop ) { + var val = arr[ type === "object" ? key : prop.idx ]; + if ( val == null ) { + val = local[ prop.idx ]; + } + local[ prop.idx ] = clamp( val, prop ); + } ); + + if ( from ) { + ret = color( from( local ) ); + ret[ cache ] = local; + return ret; + } else { + return color( local ); + } + }; + + // makes red() green() blue() alpha() hue() saturation() lightness() + each( props, function( key, prop ) { + + // alpha is included in more than one space + if ( color.fn[ key ] ) { + return; + } + color.fn[ key ] = function( value ) { + var local, cur, match, fn, + vtype = getType( value ); + + if ( key === "alpha" ) { + fn = this._hsla ? "hsla" : "rgba"; + } else { + fn = spaceName; + } + local = this[ fn ](); + cur = local[ prop.idx ]; + + if ( vtype === "undefined" ) { + return cur; + } + + if ( vtype === "function" ) { + value = value.call( this, cur ); + vtype = getType( value ); + } + if ( value == null && prop.empty ) { + return this; + } + if ( vtype === "string" ) { + match = rplusequals.exec( value ); + if ( match ) { + value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); + } + } + local[ prop.idx ] = value; + return this[ fn ]( local ); + }; + } ); + } ); + +// add cssHook and .fx.step function for each named hook. // accept a space separated string of properties -color.hook = function( hook ) { - var hooks = hook.split( " " ); - each( hooks, function( i, hook ) { - jQuery.cssHooks[ hook ] = { - set: function( elem, value ) { - var parsed, curElem, - backgroundColor = ""; - - if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || - ( parsed = stringParse( value ) ) ) ) { - value = color( parsed || value ); - if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { - curElem = hook === "backgroundColor" ? elem.parentNode : elem; - while ( - ( backgroundColor === "" || backgroundColor === "transparent" ) && - curElem && curElem.style - ) { - try { - backgroundColor = jQuery.css( curElem, "backgroundColor" ); - curElem = curElem.parentNode; - } catch ( e ) { - } - } - - value = value.blend( backgroundColor && backgroundColor !== "transparent" ? - backgroundColor : - "_default" ); - } - - value = value.toRgbaString(); - } - try { - elem.style[ hook ] = value; - } catch ( e ) { - - // Wrapped to prevent IE from throwing errors on "invalid" values like - // 'auto' or 'inherit' - } - } - }; - jQuery.fx.step[ hook ] = function( fx ) { - if ( !fx.colorInit ) { - fx.start = color( fx.elem, hook ); - fx.end = color( fx.end ); - fx.colorInit = true; - } - jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); - }; - } ); - -}; - -color.hook( stepHooks ); - -jQuery.cssHooks.borderColor = { - expand: function( value ) { - var expanded = {}; - - each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { - expanded[ "border" + part + "Color" ] = value; - } ); - return expanded; - } -}; + color.hook = function( hook ) { + var hooks = hook.split( " " ); + each( hooks, function( _i, hook ) { + jQuery.cssHooks[ hook ] = { + set: function( elem, value ) { + var parsed, curElem, + backgroundColor = ""; + + if ( value !== "transparent" && ( getType( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { + value = color( parsed || value ); + if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { + curElem = hook === "backgroundColor" ? elem.parentNode : elem; + while ( + ( backgroundColor === "" || backgroundColor === "transparent" ) && + curElem && curElem.style + ) { + try { + backgroundColor = jQuery.css( curElem, "backgroundColor" ); + curElem = curElem.parentNode; + } catch ( e ) { + } + } + + value = value.blend( backgroundColor && backgroundColor !== "transparent" ? + backgroundColor : + "_default" ); + } + + value = value.toRgbaString(); + } + try { + elem.style[ hook ] = value; + } catch ( e ) { + + // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' + } + } + }; + jQuery.fx.step[ hook ] = function( fx ) { + if ( !fx.colorInit ) { + fx.start = color( fx.elem, hook ); + fx.end = color( fx.end ); + fx.colorInit = true; + } + jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); + }; + } ); + + }; + + color.hook( stepHooks ); + + jQuery.cssHooks.borderColor = { + expand: function( value ) { + var expanded = {}; + + each( [ "Top", "Right", "Bottom", "Left" ], function( _i, part ) { + expanded[ "border" + part + "Color" ] = value; + } ); + return expanded; + } + }; // Basic color names only. // Usage of any of the other color names requires adding yourself or including // jquery.color.svg-names.js. -colors = jQuery.Color.names = { - - // 4.1. Basic color keywords - aqua: "#00ffff", - black: "#000000", - blue: "#0000ff", - fuchsia: "#ff00ff", - gray: "#808080", - green: "#008000", - lime: "#00ff00", - maroon: "#800000", - navy: "#000080", - olive: "#808000", - purple: "#800080", - red: "#ff0000", - silver: "#c0c0c0", - teal: "#008080", - white: "#ffffff", - yellow: "#ffff00", - - // 4.2.3. "transparent" color keyword - transparent: [ null, null, null, 0 ], - - _default: "#ffffff" -}; - -} )( jQuery ); - -/******************************************************************************/ -/****************************** CLASS ANIMATIONS ******************************/ -/******************************************************************************/ -( function() { - -var classAnimationActions = [ "add", "remove", "toggle" ], - shorthandStyles = { - border: 1, - borderBottom: 1, - borderColor: 1, - borderLeft: 1, - borderRight: 1, - borderTop: 1, - borderWidth: 1, - margin: 1, - padding: 1 - }; - -$.each( - [ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], - function( _, prop ) { - $.fx.step[ prop ] = function( fx ) { - if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { - jQuery.style( fx.elem, prop, fx.end ); - fx.setAttr = true; - } - }; - } -); - -function getElementStyles( elem ) { - var key, len, - style = elem.ownerDocument.defaultView ? - elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : - elem.currentStyle, - styles = {}; - - if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { - len = style.length; - while ( len-- ) { - key = style[ len ]; - if ( typeof style[ key ] === "string" ) { - styles[ $.camelCase( key ) ] = style[ key ]; - } - } - - // Support: Opera, IE <9 - } else { - for ( key in style ) { - if ( typeof style[ key ] === "string" ) { - styles[ key ] = style[ key ]; - } - } - } - - return styles; -} - -function styleDifference( oldStyle, newStyle ) { - var diff = {}, - name, value; - - for ( name in newStyle ) { - value = newStyle[ name ]; - if ( oldStyle[ name ] !== value ) { - if ( !shorthandStyles[ name ] ) { - if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { - diff[ name ] = value; - } - } - } - } - - return diff; -} - -// Support: jQuery <1.8 -if ( !$.fn.addBack ) { - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - -$.effects.animateClass = function( value, duration, easing, callback ) { - var o = $.speed( duration, easing, callback ); - - return this.queue( function() { - var animated = $( this ), - baseClass = animated.attr( "class" ) || "", - applyClassChange, - allAnimations = o.children ? animated.find( "*" ).addBack() : animated; - - // Map the animated objects to store the original styles. - allAnimations = allAnimations.map( function() { - var el = $( this ); - return { - el: el, - start: getElementStyles( this ) - }; - } ); - - // Apply class change - applyClassChange = function() { - $.each( classAnimationActions, function( i, action ) { - if ( value[ action ] ) { - animated[ action + "Class" ]( value[ action ] ); - } - } ); - }; - applyClassChange(); - - // Map all animated objects again - calculate new styles and diff - allAnimations = allAnimations.map( function() { - this.end = getElementStyles( this.el[ 0 ] ); - this.diff = styleDifference( this.start, this.end ); - return this; - } ); - - // Apply original class - animated.attr( "class", baseClass ); - - // Map all animated objects again - this time collecting a promise - allAnimations = allAnimations.map( function() { - var styleInfo = this, - dfd = $.Deferred(), - opts = $.extend( {}, o, { - queue: false, - complete: function() { - dfd.resolve( styleInfo ); - } - } ); - - this.el.animate( this.diff, opts ); - return dfd.promise(); - } ); - - // Once all animations have completed: - $.when.apply( $, allAnimations.get() ).done( function() { - - // Set the final class - applyClassChange(); - - // For each animated element, - // clear all css properties that were animated - $.each( arguments, function() { - var el = this.el; - $.each( this.diff, function( key ) { - el.css( key, "" ); - } ); - } ); - - // This is guarnteed to be there if you use jQuery.speed() - // it also handles dequeuing the next anim... - o.complete.call( animated[ 0 ] ); - } ); - } ); -}; - -$.fn.extend( { - addClass: ( function( orig ) { - return function( classNames, speed, easing, callback ) { - return speed ? - $.effects.animateClass.call( this, - { add: classNames }, speed, easing, callback ) : - orig.apply( this, arguments ); - }; - } )( $.fn.addClass ), - - removeClass: ( function( orig ) { - return function( classNames, speed, easing, callback ) { - return arguments.length > 1 ? - $.effects.animateClass.call( this, - { remove: classNames }, speed, easing, callback ) : - orig.apply( this, arguments ); - }; - } )( $.fn.removeClass ), - - toggleClass: ( function( orig ) { - return function( classNames, force, speed, easing, callback ) { - if ( typeof force === "boolean" || force === undefined ) { - if ( !speed ) { - - // Without speed parameter - return orig.apply( this, arguments ); - } else { - return $.effects.animateClass.call( this, - ( force ? { add: classNames } : { remove: classNames } ), - speed, easing, callback ); - } - } else { - - // Without force parameter - return $.effects.animateClass.call( this, - { toggle: classNames }, force, speed, easing ); - } - }; - } )( $.fn.toggleClass ), - - switchClass: function( remove, add, speed, easing, callback ) { - return $.effects.animateClass.call( this, { - add: add, - remove: remove - }, speed, easing, callback ); - } -} ); - -} )(); - -/******************************************************************************/ -/*********************************** EFFECTS **********************************/ -/******************************************************************************/ - -( function() { - -if ( $.expr && $.expr.filters && $.expr.filters.animated ) { - $.expr.filters.animated = ( function( orig ) { - return function( elem ) { - return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); - }; - } )( $.expr.filters.animated ); -} - -if ( $.uiBackCompat !== false ) { - $.extend( $.effects, { - - // Saves a set of properties in a data storage - save: function( element, set ) { - var i = 0, length = set.length; - for ( ; i < length; i++ ) { - if ( set[ i ] !== null ) { - element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); - } - } - }, - - // Restores a set of previously saved properties from a data storage - restore: function( element, set ) { - var val, i = 0, length = set.length; - for ( ; i < length; i++ ) { - if ( set[ i ] !== null ) { - val = element.data( dataSpace + set[ i ] ); - element.css( set[ i ], val ); - } - } - }, - - setMode: function( el, mode ) { - if ( mode === "toggle" ) { - mode = el.is( ":hidden" ) ? "show" : "hide"; - } - return mode; - }, - - // Wraps the element around a wrapper that copies position properties - createWrapper: function( element ) { - - // If the element is already wrapped, return it - if ( element.parent().is( ".ui-effects-wrapper" ) ) { - return element.parent(); - } - - // Wrap the element - var props = { - width: element.outerWidth( true ), - height: element.outerHeight( true ), - "float": element.css( "float" ) - }, - wrapper = $( "<div></div>" ) - .addClass( "ui-effects-wrapper" ) - .css( { - fontSize: "100%", - background: "transparent", - border: "none", - margin: 0, - padding: 0 - } ), - - // Store the size in case width/height are defined in % - Fixes #5245 - size = { - width: element.width(), - height: element.height() - }, - active = document.activeElement; - - // Support: Firefox - // Firefox incorrectly exposes anonymous content - // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 - try { - active.id; - } catch ( e ) { - active = document.body; - } - - element.wrap( wrapper ); - - // Fixes #7595 - Elements lose focus when wrapped. - if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { - $( active ).trigger( "focus" ); - } - - // Hotfix for jQuery 1.4 since some change in wrap() seems to actually - // lose the reference to the wrapped element - wrapper = element.parent(); - - // Transfer positioning properties to the wrapper - if ( element.css( "position" ) === "static" ) { - wrapper.css( { position: "relative" } ); - element.css( { position: "relative" } ); - } else { - $.extend( props, { - position: element.css( "position" ), - zIndex: element.css( "z-index" ) - } ); - $.each( [ "top", "left", "bottom", "right" ], function( i, pos ) { - props[ pos ] = element.css( pos ); - if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { - props[ pos ] = "auto"; - } - } ); - element.css( { - position: "relative", - top: 0, - left: 0, - right: "auto", - bottom: "auto" - } ); - } - element.css( size ); - - return wrapper.css( props ).show(); - }, - - removeWrapper: function( element ) { - var active = document.activeElement; - - if ( element.parent().is( ".ui-effects-wrapper" ) ) { - element.parent().replaceWith( element ); - - // Fixes #7595 - Elements lose focus when wrapped. - if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { - $( active ).trigger( "focus" ); - } - } - - return element; - } - } ); -} - -$.extend( $.effects, { - version: "1.12.1", - - define: function( name, mode, effect ) { - if ( !effect ) { - effect = mode; - mode = "effect"; - } - - $.effects.effect[ name ] = effect; - $.effects.effect[ name ].mode = mode; - - return effect; - }, - - scaledDimensions: function( element, percent, direction ) { - if ( percent === 0 ) { - return { - height: 0, - width: 0, - outerHeight: 0, - outerWidth: 0 - }; - } - - var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1, - y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1; - - return { - height: element.height() * y, - width: element.width() * x, - outerHeight: element.outerHeight() * y, - outerWidth: element.outerWidth() * x - }; - - }, - - clipToBox: function( animation ) { - return { - width: animation.clip.right - animation.clip.left, - height: animation.clip.bottom - animation.clip.top, - left: animation.clip.left, - top: animation.clip.top - }; - }, - - // Injects recently queued functions to be first in line (after "inprogress") - unshift: function( element, queueLength, count ) { - var queue = element.queue(); - - if ( queueLength > 1 ) { - queue.splice.apply( queue, - [ 1, 0 ].concat( queue.splice( queueLength, count ) ) ); - } - element.dequeue(); - }, - - saveStyle: function( element ) { - element.data( dataSpaceStyle, element[ 0 ].style.cssText ); - }, - - restoreStyle: function( element ) { - element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || ""; - element.removeData( dataSpaceStyle ); - }, - - mode: function( element, mode ) { - var hidden = element.is( ":hidden" ); - - if ( mode === "toggle" ) { - mode = hidden ? "show" : "hide"; - } - if ( hidden ? mode === "hide" : mode === "show" ) { - mode = "none"; - } - return mode; - }, - - // Translates a [top,left] array into a baseline value - getBaseline: function( origin, original ) { - var y, x; - - switch ( origin[ 0 ] ) { - case "top": - y = 0; - break; - case "middle": - y = 0.5; - break; - case "bottom": - y = 1; - break; - default: - y = origin[ 0 ] / original.height; - } - - switch ( origin[ 1 ] ) { - case "left": - x = 0; - break; - case "center": - x = 0.5; - break; - case "right": - x = 1; - break; - default: - x = origin[ 1 ] / original.width; - } - - return { - x: x, - y: y - }; - }, - - // Creates a placeholder element so that the original element can be made absolute - createPlaceholder: function( element ) { - var placeholder, - cssPosition = element.css( "position" ), - position = element.position(); - - // Lock in margins first to account for form elements, which - // will change margin if you explicitly set height - // see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380 - // Support: Safari - element.css( { - marginTop: element.css( "marginTop" ), - marginBottom: element.css( "marginBottom" ), - marginLeft: element.css( "marginLeft" ), - marginRight: element.css( "marginRight" ) - } ) - .outerWidth( element.outerWidth() ) - .outerHeight( element.outerHeight() ); - - if ( /^(static|relative)/.test( cssPosition ) ) { - cssPosition = "absolute"; - - placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( { - - // Convert inline to inline block to account for inline elements - // that turn to inline block based on content (like img) - display: /^(inline|ruby)/.test( element.css( "display" ) ) ? - "inline-block" : - "block", - visibility: "hidden", - - // Margins need to be set to account for margin collapse - marginTop: element.css( "marginTop" ), - marginBottom: element.css( "marginBottom" ), - marginLeft: element.css( "marginLeft" ), - marginRight: element.css( "marginRight" ), - "float": element.css( "float" ) - } ) - .outerWidth( element.outerWidth() ) - .outerHeight( element.outerHeight() ) - .addClass( "ui-effects-placeholder" ); - - element.data( dataSpace + "placeholder", placeholder ); - } - - element.css( { - position: cssPosition, - left: position.left, - top: position.top - } ); - - return placeholder; - }, - - removePlaceholder: function( element ) { - var dataKey = dataSpace + "placeholder", - placeholder = element.data( dataKey ); - - if ( placeholder ) { - placeholder.remove(); - element.removeData( dataKey ); - } - }, - - // Removes a placeholder if it exists and restores - // properties that were modified during placeholder creation - cleanUp: function( element ) { - $.effects.restoreStyle( element ); - $.effects.removePlaceholder( element ); - }, - - setTransition: function( element, list, factor, value ) { - value = value || {}; - $.each( list, function( i, x ) { - var unit = element.cssUnit( x ); - if ( unit[ 0 ] > 0 ) { - value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; - } - } ); - return value; - } -} ); - -// Return an effect options object for the given parameters: -function _normalizeArguments( effect, options, speed, callback ) { - - // Allow passing all options as the first parameter - if ( $.isPlainObject( effect ) ) { - options = effect; - effect = effect.effect; - } - - // Convert to an object - effect = { effect: effect }; - - // Catch (effect, null, ...) - if ( options == null ) { - options = {}; - } - - // Catch (effect, callback) - if ( $.isFunction( options ) ) { - callback = options; - speed = null; - options = {}; - } - - // Catch (effect, speed, ?) - if ( typeof options === "number" || $.fx.speeds[ options ] ) { - callback = speed; - speed = options; - options = {}; - } - - // Catch (effect, options, callback) - if ( $.isFunction( speed ) ) { - callback = speed; - speed = null; - } - - // Add options to effect - if ( options ) { - $.extend( effect, options ); - } - - speed = speed || options.duration; - effect.duration = $.fx.off ? 0 : - typeof speed === "number" ? speed : - speed in $.fx.speeds ? $.fx.speeds[ speed ] : - $.fx.speeds._default; - - effect.complete = callback || options.complete; - - return effect; -} - -function standardAnimationOption( option ) { - - // Valid standard speeds (nothing, number, named speed) - if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { - return true; - } - - // Invalid strings - treat as "normal" speed - if ( typeof option === "string" && !$.effects.effect[ option ] ) { - return true; - } - - // Complete callback - if ( $.isFunction( option ) ) { - return true; - } - - // Options hash (but not naming an effect) - if ( typeof option === "object" && !option.effect ) { - return true; - } - - // Didn't match any standard API - return false; -} - -$.fn.extend( { - effect: function( /* effect, options, speed, callback */ ) { - var args = _normalizeArguments.apply( this, arguments ), - effectMethod = $.effects.effect[ args.effect ], - defaultMode = effectMethod.mode, - queue = args.queue, - queueName = queue || "fx", - complete = args.complete, - mode = args.mode, - modes = [], - prefilter = function( next ) { - var el = $( this ), - normalizedMode = $.effects.mode( el, mode ) || defaultMode; - - // Sentinel for duck-punching the :animated psuedo-selector - el.data( dataSpaceAnimated, true ); - - // Save effect mode for later use, - // we can't just call $.effects.mode again later, - // as the .show() below destroys the initial state - modes.push( normalizedMode ); - - // See $.uiBackCompat inside of run() for removal of defaultMode in 1.13 - if ( defaultMode && ( normalizedMode === "show" || - ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { - el.show(); - } - - if ( !defaultMode || normalizedMode !== "none" ) { - $.effects.saveStyle( el ); - } - - if ( $.isFunction( next ) ) { - next(); - } - }; - - if ( $.fx.off || !effectMethod ) { - - // Delegate to the original method (e.g., .show()) if possible - if ( mode ) { - return this[ mode ]( args.duration, complete ); - } else { - return this.each( function() { - if ( complete ) { - complete.call( this ); - } - } ); - } - } - - function run( next ) { - var elem = $( this ); - - function cleanup() { - elem.removeData( dataSpaceAnimated ); - - $.effects.cleanUp( elem ); - - if ( args.mode === "hide" ) { - elem.hide(); - } - - done(); - } - - function done() { - if ( $.isFunction( complete ) ) { - complete.call( elem[ 0 ] ); - } - - if ( $.isFunction( next ) ) { - next(); - } - } - - // Override mode option on a per element basis, - // as toggle can be either show or hide depending on element state - args.mode = modes.shift(); - - if ( $.uiBackCompat !== false && !defaultMode ) { - if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { - - // Call the core method to track "olddisplay" properly - elem[ mode ](); - done(); - } else { - effectMethod.call( elem[ 0 ], args, done ); - } - } else { - if ( args.mode === "none" ) { - - // Call the core method to track "olddisplay" properly - elem[ mode ](); - done(); - } else { - effectMethod.call( elem[ 0 ], args, cleanup ); - } - } - } - - // Run prefilter on all elements first to ensure that - // any showing or hiding happens before placeholder creation, - // which ensures that any layout changes are correctly captured. - return queue === false ? - this.each( prefilter ).each( run ) : - this.queue( queueName, prefilter ).queue( queueName, run ); - }, - - show: ( function( orig ) { - return function( option ) { - if ( standardAnimationOption( option ) ) { - return orig.apply( this, arguments ); - } else { - var args = _normalizeArguments.apply( this, arguments ); - args.mode = "show"; - return this.effect.call( this, args ); - } - }; - } )( $.fn.show ), - - hide: ( function( orig ) { - return function( option ) { - if ( standardAnimationOption( option ) ) { - return orig.apply( this, arguments ); - } else { - var args = _normalizeArguments.apply( this, arguments ); - args.mode = "hide"; - return this.effect.call( this, args ); - } - }; - } )( $.fn.hide ), - - toggle: ( function( orig ) { - return function( option ) { - if ( standardAnimationOption( option ) || typeof option === "boolean" ) { - return orig.apply( this, arguments ); - } else { - var args = _normalizeArguments.apply( this, arguments ); - args.mode = "toggle"; - return this.effect.call( this, args ); - } - }; - } )( $.fn.toggle ), - - cssUnit: function( key ) { - var style = this.css( key ), - val = []; - - $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { - if ( style.indexOf( unit ) > 0 ) { - val = [ parseFloat( style ), unit ]; - } - } ); - return val; - }, - - cssClip: function( clipObj ) { - if ( clipObj ) { - return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " + - clipObj.bottom + "px " + clipObj.left + "px)" ); - } - return parseClip( this.css( "clip" ), this ); - }, - - transfer: function( options, done ) { - var element = $( this ), - target = $( options.to ), - targetFixed = target.css( "position" ) === "fixed", - body = $( "body" ), - fixTop = targetFixed ? body.scrollTop() : 0, - fixLeft = targetFixed ? body.scrollLeft() : 0, - endPosition = target.offset(), - animation = { - top: endPosition.top - fixTop, - left: endPosition.left - fixLeft, - height: target.innerHeight(), - width: target.innerWidth() - }, - startPosition = element.offset(), - transfer = $( "<div class='ui-effects-transfer'></div>" ) - .appendTo( "body" ) - .addClass( options.className ) - .css( { - top: startPosition.top - fixTop, - left: startPosition.left - fixLeft, - height: element.innerHeight(), - width: element.innerWidth(), - position: targetFixed ? "fixed" : "absolute" - } ) - .animate( animation, options.duration, options.easing, function() { - transfer.remove(); - if ( $.isFunction( done ) ) { - done(); - } - } ); - } -} ); - -function parseClip( str, element ) { - var outerWidth = element.outerWidth(), - outerHeight = element.outerHeight(), - clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/, - values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ]; - - return { - top: parseFloat( values[ 1 ] ) || 0, - right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ), - bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ), - left: parseFloat( values[ 4 ] ) || 0 - }; -} - -$.fx.step.clip = function( fx ) { - if ( !fx.clipInit ) { - fx.start = $( fx.elem ).cssClip(); - if ( typeof fx.end === "string" ) { - fx.end = parseClip( fx.end, fx.elem ); - } - fx.clipInit = true; - } - - $( fx.elem ).cssClip( { - top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top, - right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right, - bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom, - left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left - } ); -}; - -} )(); - -/******************************************************************************/ -/*********************************** EASING ***********************************/ -/******************************************************************************/ - -( function() { - -// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing) - -var baseEasings = {}; + colors = jQuery.Color.names = { + + // 4.1. Basic color keywords + aqua: "#00ffff", + black: "#000000", + blue: "#0000ff", + fuchsia: "#ff00ff", + gray: "#808080", + green: "#008000", + lime: "#00ff00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + purple: "#800080", + red: "#ff0000", + silver: "#c0c0c0", + teal: "#008080", + white: "#ffffff", + yellow: "#ffff00", + + // 4.2.3. "transparent" color keyword + transparent: [ null, null, null, 0 ], + + _default: "#ffffff" + }; + + + /*! + * jQuery UI Effects 1.13.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ -$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { - baseEasings[ name ] = function( p ) { - return Math.pow( p, i + 2 ); - }; -} ); +//>>label: Effects Core +//>>group: Effects + /* eslint-disable max-len */ +//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects. + /* eslint-enable max-len */ +//>>docs: http://api.jqueryui.com/category/effects-core/ +//>>demos: http://jqueryui.com/effect/ -$.extend( baseEasings, { - Sine: function( p ) { - return 1 - Math.cos( p * Math.PI / 2 ); - }, - Circ: function( p ) { - return 1 - Math.sqrt( 1 - p * p ); - }, - Elastic: function( p ) { - return p === 0 || p === 1 ? p : - -Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 ); - }, - Back: function( p ) { - return p * p * ( 3 * p - 2 ); - }, - Bounce: function( p ) { - var pow2, - bounce = 4; - - while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} - return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); - } -} ); -$.each( baseEasings, function( name, easeIn ) { - $.easing[ "easeIn" + name ] = easeIn; - $.easing[ "easeOut" + name ] = function( p ) { - return 1 - easeIn( 1 - p ); - }; - $.easing[ "easeInOut" + name ] = function( p ) { - return p < 0.5 ? - easeIn( p * 2 ) / 2 : - 1 - easeIn( p * -2 + 2 ) / 2; - }; -} ); + var dataSpace = "ui-effects-", + dataSpaceStyle = "ui-effects-style", + dataSpaceAnimated = "ui-effects-animated"; + + $.effects = { + effect: {} + }; + + /******************************************************************************/ + /****************************** CLASS ANIMATIONS ******************************/ + /******************************************************************************/ + ( function() { + + var classAnimationActions = [ "add", "remove", "toggle" ], + shorthandStyles = { + border: 1, + borderBottom: 1, + borderColor: 1, + borderLeft: 1, + borderRight: 1, + borderTop: 1, + borderWidth: 1, + margin: 1, + padding: 1 + }; + + $.each( + [ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], + function( _, prop ) { + $.fx.step[ prop ] = function( fx ) { + if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { + jQuery.style( fx.elem, prop, fx.end ); + fx.setAttr = true; + } + }; + } + ); + + function camelCase( string ) { + return string.replace( /-([\da-z])/gi, function( all, letter ) { + return letter.toUpperCase(); + } ); + } + + function getElementStyles( elem ) { + var key, len, + style = elem.ownerDocument.defaultView ? + elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : + elem.currentStyle, + styles = {}; + + if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { + len = style.length; + while ( len-- ) { + key = style[ len ]; + if ( typeof style[ key ] === "string" ) { + styles[ camelCase( key ) ] = style[ key ]; + } + } + + // Support: Opera, IE <9 + } else { + for ( key in style ) { + if ( typeof style[ key ] === "string" ) { + styles[ key ] = style[ key ]; + } + } + } + + return styles; + } + + function styleDifference( oldStyle, newStyle ) { + var diff = {}, + name, value; + + for ( name in newStyle ) { + value = newStyle[ name ]; + if ( oldStyle[ name ] !== value ) { + if ( !shorthandStyles[ name ] ) { + if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { + diff[ name ] = value; + } + } + } + } + + return diff; + } -} )(); +// Support: jQuery <1.8 + if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; + } + + $.effects.animateClass = function( value, duration, easing, callback ) { + var o = $.speed( duration, easing, callback ); + + return this.queue( function() { + var animated = $( this ), + baseClass = animated.attr( "class" ) || "", + applyClassChange, + allAnimations = o.children ? animated.find( "*" ).addBack() : animated; + + // Map the animated objects to store the original styles. + allAnimations = allAnimations.map( function() { + var el = $( this ); + return { + el: el, + start: getElementStyles( this ) + }; + } ); + + // Apply class change + applyClassChange = function() { + $.each( classAnimationActions, function( i, action ) { + if ( value[ action ] ) { + animated[ action + "Class" ]( value[ action ] ); + } + } ); + }; + applyClassChange(); + + // Map all animated objects again - calculate new styles and diff + allAnimations = allAnimations.map( function() { + this.end = getElementStyles( this.el[ 0 ] ); + this.diff = styleDifference( this.start, this.end ); + return this; + } ); + + // Apply original class + animated.attr( "class", baseClass ); + + // Map all animated objects again - this time collecting a promise + allAnimations = allAnimations.map( function() { + var styleInfo = this, + dfd = $.Deferred(), + opts = $.extend( {}, o, { + queue: false, + complete: function() { + dfd.resolve( styleInfo ); + } + } ); + + this.el.animate( this.diff, opts ); + return dfd.promise(); + } ); + + // Once all animations have completed: + $.when.apply( $, allAnimations.get() ).done( function() { + + // Set the final class + applyClassChange(); + + // For each animated element, + // clear all css properties that were animated + $.each( arguments, function() { + var el = this.el; + $.each( this.diff, function( key ) { + el.css( key, "" ); + } ); + } ); + + // This is guarnteed to be there if you use jQuery.speed() + // it also handles dequeuing the next anim... + o.complete.call( animated[ 0 ] ); + } ); + } ); + }; + + $.fn.extend( { + addClass: ( function( orig ) { + return function( classNames, speed, easing, callback ) { + return speed ? + $.effects.animateClass.call( this, + { add: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + } )( $.fn.addClass ), + + removeClass: ( function( orig ) { + return function( classNames, speed, easing, callback ) { + return arguments.length > 1 ? + $.effects.animateClass.call( this, + { remove: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + } )( $.fn.removeClass ), + + toggleClass: ( function( orig ) { + return function( classNames, force, speed, easing, callback ) { + if ( typeof force === "boolean" || force === undefined ) { + if ( !speed ) { + + // Without speed parameter + return orig.apply( this, arguments ); + } else { + return $.effects.animateClass.call( this, + ( force ? { add: classNames } : { remove: classNames } ), + speed, easing, callback ); + } + } else { + + // Without force parameter + return $.effects.animateClass.call( this, + { toggle: classNames }, force, speed, easing ); + } + }; + } )( $.fn.toggleClass ), + + switchClass: function( remove, add, speed, easing, callback ) { + return $.effects.animateClass.call( this, { + add: add, + remove: remove + }, speed, easing, callback ); + } + } ); + + } )(); + + /******************************************************************************/ + /*********************************** EFFECTS **********************************/ + /******************************************************************************/ + + ( function() { + + if ( $.expr && $.expr.pseudos && $.expr.pseudos.animated ) { + $.expr.pseudos.animated = ( function( orig ) { + return function( elem ) { + return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); + }; + } )( $.expr.pseudos.animated ); + } + + if ( $.uiBackCompat !== false ) { + $.extend( $.effects, { + + // Saves a set of properties in a data storage + save: function( element, set ) { + var i = 0, length = set.length; + for ( ; i < length; i++ ) { + if ( set[ i ] !== null ) { + element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); + } + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function( element, set ) { + var val, i = 0, length = set.length; + for ( ; i < length; i++ ) { + if ( set[ i ] !== null ) { + val = element.data( dataSpace + set[ i ] ); + element.css( set[ i ], val ); + } + } + }, + + setMode: function( el, mode ) { + if ( mode === "toggle" ) { + mode = el.is( ":hidden" ) ? "show" : "hide"; + } + return mode; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function( element ) { + + // If the element is already wrapped, return it + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + return element.parent(); + } + + // Wrap the element + var props = { + width: element.outerWidth( true ), + height: element.outerHeight( true ), + "float": element.css( "float" ) + }, + wrapper = $( "<div></div>" ) + .addClass( "ui-effects-wrapper" ) + .css( { + fontSize: "100%", + background: "transparent", + border: "none", + margin: 0, + padding: 0 + } ), + + // Store the size in case width/height are defined in % - Fixes #5245 + size = { + width: element.width(), + height: element.height() + }, + active = document.activeElement; + + // Support: Firefox + // Firefox incorrectly exposes anonymous content + // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 + try { + // eslint-disable-next-line no-unused-expressions + active.id; + } catch ( e ) { + active = document.body; + } + + element.wrap( wrapper ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).trigger( "focus" ); + } + + // Hotfix for jQuery 1.4 since some change in wrap() seems to actually + // lose the reference to the wrapped element + wrapper = element.parent(); + + // Transfer positioning properties to the wrapper + if ( element.css( "position" ) === "static" ) { + wrapper.css( { position: "relative" } ); + element.css( { position: "relative" } ); + } else { + $.extend( props, { + position: element.css( "position" ), + zIndex: element.css( "z-index" ) + } ); + $.each( [ "top", "left", "bottom", "right" ], function( i, pos ) { + props[ pos ] = element.css( pos ); + if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { + props[ pos ] = "auto"; + } + } ); + element.css( { + position: "relative", + top: 0, + left: 0, + right: "auto", + bottom: "auto" + } ); + } + element.css( size ); + + return wrapper.css( props ).show(); + }, + + removeWrapper: function( element ) { + var active = document.activeElement; + + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + element.parent().replaceWith( element ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).trigger( "focus" ); + } + } + + return element; + } + } ); + } + + $.extend( $.effects, { + version: "1.13.0", + + define: function( name, mode, effect ) { + if ( !effect ) { + effect = mode; + mode = "effect"; + } + + $.effects.effect[ name ] = effect; + $.effects.effect[ name ].mode = mode; + + return effect; + }, + + scaledDimensions: function( element, percent, direction ) { + if ( percent === 0 ) { + return { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + }; + } + + var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1, + y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1; + + return { + height: element.height() * y, + width: element.width() * x, + outerHeight: element.outerHeight() * y, + outerWidth: element.outerWidth() * x + }; + + }, + + clipToBox: function( animation ) { + return { + width: animation.clip.right - animation.clip.left, + height: animation.clip.bottom - animation.clip.top, + left: animation.clip.left, + top: animation.clip.top + }; + }, + + // Injects recently queued functions to be first in line (after "inprogress") + unshift: function( element, queueLength, count ) { + var queue = element.queue(); + + if ( queueLength > 1 ) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queueLength, count ) ) ); + } + element.dequeue(); + }, + + saveStyle: function( element ) { + element.data( dataSpaceStyle, element[ 0 ].style.cssText ); + }, + + restoreStyle: function( element ) { + element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || ""; + element.removeData( dataSpaceStyle ); + }, + + mode: function( element, mode ) { + var hidden = element.is( ":hidden" ); + + if ( mode === "toggle" ) { + mode = hidden ? "show" : "hide"; + } + if ( hidden ? mode === "hide" : mode === "show" ) { + mode = "none"; + } + return mode; + }, + + // Translates a [top,left] array into a baseline value + getBaseline: function( origin, original ) { + var y, x; + + switch ( origin[ 0 ] ) { + case "top": + y = 0; + break; + case "middle": + y = 0.5; + break; + case "bottom": + y = 1; + break; + default: + y = origin[ 0 ] / original.height; + } + + switch ( origin[ 1 ] ) { + case "left": + x = 0; + break; + case "center": + x = 0.5; + break; + case "right": + x = 1; + break; + default: + x = origin[ 1 ] / original.width; + } + + return { + x: x, + y: y + }; + }, + + // Creates a placeholder element so that the original element can be made absolute + createPlaceholder: function( element ) { + var placeholder, + cssPosition = element.css( "position" ), + position = element.position(); + + // Lock in margins first to account for form elements, which + // will change margin if you explicitly set height + // see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380 + // Support: Safari + element.css( { + marginTop: element.css( "marginTop" ), + marginBottom: element.css( "marginBottom" ), + marginLeft: element.css( "marginLeft" ), + marginRight: element.css( "marginRight" ) + } ) + .outerWidth( element.outerWidth() ) + .outerHeight( element.outerHeight() ); + + if ( /^(static|relative)/.test( cssPosition ) ) { + cssPosition = "absolute"; + + placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( { + + // Convert inline to inline block to account for inline elements + // that turn to inline block based on content (like img) + display: /^(inline|ruby)/.test( element.css( "display" ) ) ? + "inline-block" : + "block", + visibility: "hidden", + + // Margins need to be set to account for margin collapse + marginTop: element.css( "marginTop" ), + marginBottom: element.css( "marginBottom" ), + marginLeft: element.css( "marginLeft" ), + marginRight: element.css( "marginRight" ), + "float": element.css( "float" ) + } ) + .outerWidth( element.outerWidth() ) + .outerHeight( element.outerHeight() ) + .addClass( "ui-effects-placeholder" ); + + element.data( dataSpace + "placeholder", placeholder ); + } + + element.css( { + position: cssPosition, + left: position.left, + top: position.top + } ); + + return placeholder; + }, + + removePlaceholder: function( element ) { + var dataKey = dataSpace + "placeholder", + placeholder = element.data( dataKey ); + + if ( placeholder ) { + placeholder.remove(); + element.removeData( dataKey ); + } + }, + + // Removes a placeholder if it exists and restores + // properties that were modified during placeholder creation + cleanUp: function( element ) { + $.effects.restoreStyle( element ); + $.effects.removePlaceholder( element ); + }, + + setTransition: function( element, list, factor, value ) { + value = value || {}; + $.each( list, function( i, x ) { + var unit = element.cssUnit( x ); + if ( unit[ 0 ] > 0 ) { + value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; + } + } ); + return value; + } + } ); -var effect = $.effects; +// Return an effect options object for the given parameters: + function _normalizeArguments( effect, options, speed, callback ) { + + // Allow passing all options as the first parameter + if ( $.isPlainObject( effect ) ) { + options = effect; + effect = effect.effect; + } + + // Convert to an object + effect = { effect: effect }; + + // Catch (effect, null, ...) + if ( options == null ) { + options = {}; + } + + // Catch (effect, callback) + if ( typeof options === "function" ) { + callback = options; + speed = null; + options = {}; + } + + // Catch (effect, speed, ?) + if ( typeof options === "number" || $.fx.speeds[ options ] ) { + callback = speed; + speed = options; + options = {}; + } + + // Catch (effect, options, callback) + if ( typeof speed === "function" ) { + callback = speed; + speed = null; + } + + // Add options to effect + if ( options ) { + $.extend( effect, options ); + } + + speed = speed || options.duration; + effect.duration = $.fx.off ? 0 : + typeof speed === "number" ? speed : + speed in $.fx.speeds ? $.fx.speeds[ speed ] : + $.fx.speeds._default; + + effect.complete = callback || options.complete; + + return effect; + } + + function standardAnimationOption( option ) { + + // Valid standard speeds (nothing, number, named speed) + if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { + return true; + } + + // Invalid strings - treat as "normal" speed + if ( typeof option === "string" && !$.effects.effect[ option ] ) { + return true; + } + + // Complete callback + if ( typeof option === "function" ) { + return true; + } + + // Options hash (but not naming an effect) + if ( typeof option === "object" && !option.effect ) { + return true; + } + + // Didn't match any standard API + return false; + } + + $.fn.extend( { + effect: function( /* effect, options, speed, callback */ ) { + var args = _normalizeArguments.apply( this, arguments ), + effectMethod = $.effects.effect[ args.effect ], + defaultMode = effectMethod.mode, + queue = args.queue, + queueName = queue || "fx", + complete = args.complete, + mode = args.mode, + modes = [], + prefilter = function( next ) { + var el = $( this ), + normalizedMode = $.effects.mode( el, mode ) || defaultMode; + + // Sentinel for duck-punching the :animated pseudo-selector + el.data( dataSpaceAnimated, true ); + + // Save effect mode for later use, + // we can't just call $.effects.mode again later, + // as the .show() below destroys the initial state + modes.push( normalizedMode ); + + // See $.uiBackCompat inside of run() for removal of defaultMode in 1.14 + if ( defaultMode && ( normalizedMode === "show" || + ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { + el.show(); + } + + if ( !defaultMode || normalizedMode !== "none" ) { + $.effects.saveStyle( el ); + } + + if ( typeof next === "function" ) { + next(); + } + }; + + if ( $.fx.off || !effectMethod ) { + + // Delegate to the original method (e.g., .show()) if possible + if ( mode ) { + return this[ mode ]( args.duration, complete ); + } else { + return this.each( function() { + if ( complete ) { + complete.call( this ); + } + } ); + } + } + + function run( next ) { + var elem = $( this ); + + function cleanup() { + elem.removeData( dataSpaceAnimated ); + + $.effects.cleanUp( elem ); + + if ( args.mode === "hide" ) { + elem.hide(); + } + + done(); + } + + function done() { + if ( typeof complete === "function" ) { + complete.call( elem[ 0 ] ); + } + + if ( typeof next === "function" ) { + next(); + } + } + + // Override mode option on a per element basis, + // as toggle can be either show or hide depending on element state + args.mode = modes.shift(); + + if ( $.uiBackCompat !== false && !defaultMode ) { + if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { + + // Call the core method to track "olddisplay" properly + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[ 0 ], args, done ); + } + } else { + if ( args.mode === "none" ) { + + // Call the core method to track "olddisplay" properly + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[ 0 ], args, cleanup ); + } + } + } + + // Run prefilter on all elements first to ensure that + // any showing or hiding happens before placeholder creation, + // which ensures that any layout changes are correctly captured. + return queue === false ? + this.each( prefilter ).each( run ) : + this.queue( queueName, prefilter ).queue( queueName, run ); + }, + + show: ( function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "show"; + return this.effect.call( this, args ); + } + }; + } )( $.fn.show ), + + hide: ( function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "hide"; + return this.effect.call( this, args ); + } + }; + } )( $.fn.hide ), + + toggle: ( function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) || typeof option === "boolean" ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "toggle"; + return this.effect.call( this, args ); + } + }; + } )( $.fn.toggle ), + + cssUnit: function( key ) { + var style = this.css( key ), + val = []; + + $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { + if ( style.indexOf( unit ) > 0 ) { + val = [ parseFloat( style ), unit ]; + } + } ); + return val; + }, + + cssClip: function( clipObj ) { + if ( clipObj ) { + return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " + + clipObj.bottom + "px " + clipObj.left + "px)" ); + } + return parseClip( this.css( "clip" ), this ); + }, + + transfer: function( options, done ) { + var element = $( this ), + target = $( options.to ), + targetFixed = target.css( "position" ) === "fixed", + body = $( "body" ), + fixTop = targetFixed ? body.scrollTop() : 0, + fixLeft = targetFixed ? body.scrollLeft() : 0, + endPosition = target.offset(), + animation = { + top: endPosition.top - fixTop, + left: endPosition.left - fixLeft, + height: target.innerHeight(), + width: target.innerWidth() + }, + startPosition = element.offset(), + transfer = $( "<div class='ui-effects-transfer'></div>" ); + + transfer + .appendTo( "body" ) + .addClass( options.className ) + .css( { + top: startPosition.top - fixTop, + left: startPosition.left - fixLeft, + height: element.innerHeight(), + width: element.innerWidth(), + position: targetFixed ? "fixed" : "absolute" + } ) + .animate( animation, options.duration, options.easing, function() { + transfer.remove(); + if ( typeof done === "function" ) { + done(); + } + } ); + } + } ); + + function parseClip( str, element ) { + var outerWidth = element.outerWidth(), + outerHeight = element.outerHeight(), + clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/, + values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ]; + + return { + top: parseFloat( values[ 1 ] ) || 0, + right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ), + bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ), + left: parseFloat( values[ 4 ] ) || 0 + }; + } + + $.fx.step.clip = function( fx ) { + if ( !fx.clipInit ) { + fx.start = $( fx.elem ).cssClip(); + if ( typeof fx.end === "string" ) { + fx.end = parseClip( fx.end, fx.elem ); + } + fx.clipInit = true; + } + + $( fx.elem ).cssClip( { + top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top, + right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right, + bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom, + left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left + } ); + }; + + } )(); + + /******************************************************************************/ + /*********************************** EASING ***********************************/ + /******************************************************************************/ + + ( function() { +// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing) -/*! - * jQuery UI Effects Blind 1.12.1 + var baseEasings = {}; + + $.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { + baseEasings[ name ] = function( p ) { + return Math.pow( p, i + 2 ); + }; + } ); + + $.extend( baseEasings, { + Sine: function( p ) { + return 1 - Math.cos( p * Math.PI / 2 ); + }, + Circ: function( p ) { + return 1 - Math.sqrt( 1 - p * p ); + }, + Elastic: function( p ) { + return p === 0 || p === 1 ? p : + -Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 ); + }, + Back: function( p ) { + return p * p * ( 3 * p - 2 ); + }, + Bounce: function( p ) { + var pow2, + bounce = 4; + + while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} + return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); + } + } ); + + $.each( baseEasings, function( name, easeIn ) { + $.easing[ "easeIn" + name ] = easeIn; + $.easing[ "easeOut" + name ] = function( p ) { + return 1 - easeIn( 1 - p ); + }; + $.easing[ "easeInOut" + name ] = function( p ) { + return p < 0.5 ? + easeIn( p * 2 ) / 2 : + 1 - easeIn( p * -2 + 2 ) / 2; + }; + } ); + + } )(); + + var effect = $.effects; + + + /*! + * jQuery UI Effects Blind 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17768,48 +18121,47 @@ var effect = $.effects; //>>demos: http://jqueryui.com/effect/ - -var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, done ) { - var map = { - up: [ "bottom", "top" ], - vertical: [ "bottom", "top" ], - down: [ "top", "bottom" ], - left: [ "right", "left" ], - horizontal: [ "right", "left" ], - right: [ "left", "right" ] - }, - element = $( this ), - direction = options.direction || "up", - start = element.cssClip(), - animate = { clip: $.extend( {}, start ) }, - placeholder = $.effects.createPlaceholder( element ); - - animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ]; - - if ( options.mode === "show" ) { - element.cssClip( animate.clip ); - if ( placeholder ) { - placeholder.css( $.effects.clipToBox( animate ) ); - } - - animate.clip = start; - } - - if ( placeholder ) { - placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing ); - } - - element.animate( animate, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); -} ); - - -/*! - * jQuery UI Effects Bounce 1.12.1 + var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, done ) { + var map = { + up: [ "bottom", "top" ], + vertical: [ "bottom", "top" ], + down: [ "top", "bottom" ], + left: [ "right", "left" ], + horizontal: [ "right", "left" ], + right: [ "left", "right" ] + }, + element = $( this ), + direction = options.direction || "up", + start = element.cssClip(), + animate = { clip: $.extend( {}, start ) }, + placeholder = $.effects.createPlaceholder( element ); + + animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ]; + + if ( options.mode === "show" ) { + element.cssClip( animate.clip ); + if ( placeholder ) { + placeholder.css( $.effects.clipToBox( animate ) ); + } + + animate.clip = start; + } + + if ( placeholder ) { + placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing ); + } + + element.animate( animate, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); + } ); + + + /*! + * jQuery UI Effects Bounce 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17824,88 +18176,87 @@ var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, d //>>demos: http://jqueryui.com/effect/ + var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) { + var upAnim, downAnim, refValue, + element = $( this ), -var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) { - var upAnim, downAnim, refValue, - element = $( this ), + // Defaults: + mode = options.mode, + hide = mode === "hide", + show = mode === "show", + direction = options.direction || "up", + distance = options.distance, + times = options.times || 5, - // Defaults: - mode = options.mode, - hide = mode === "hide", - show = mode === "show", - direction = options.direction || "up", - distance = options.distance, - times = options.times || 5, + // Number of internal animations + anims = times * 2 + ( show || hide ? 1 : 0 ), + speed = options.duration / anims, + easing = options.easing, - // Number of internal animations - anims = times * 2 + ( show || hide ? 1 : 0 ), - speed = options.duration / anims, - easing = options.easing, + // Utility: + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ), + i = 0, - // Utility: - ref = ( direction === "up" || direction === "down" ) ? "top" : "left", - motion = ( direction === "up" || direction === "left" ), - i = 0, + queuelen = element.queue().length; - queuelen = element.queue().length; + $.effects.createPlaceholder( element ); - $.effects.createPlaceholder( element ); + refValue = element.css( ref ); - refValue = element.css( ref ); + // Default distance for the BIGGEST bounce is the outer Distance / 3 + if ( !distance ) { + distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; + } - // Default distance for the BIGGEST bounce is the outer Distance / 3 - if ( !distance ) { - distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; - } + if ( show ) { + downAnim = { opacity: 1 }; + downAnim[ ref ] = refValue; - if ( show ) { - downAnim = { opacity: 1 }; - downAnim[ ref ] = refValue; + // If we are showing, force opacity 0 and set the initial position + // then do the "first" animation + element + .css( "opacity", 0 ) + .css( ref, motion ? -distance * 2 : distance * 2 ) + .animate( downAnim, speed, easing ); + } - // If we are showing, force opacity 0 and set the initial position - // then do the "first" animation - element - .css( "opacity", 0 ) - .css( ref, motion ? -distance * 2 : distance * 2 ) - .animate( downAnim, speed, easing ); - } + // Start at the smallest distance if we are hiding + if ( hide ) { + distance = distance / Math.pow( 2, times - 1 ); + } - // Start at the smallest distance if we are hiding - if ( hide ) { - distance = distance / Math.pow( 2, times - 1 ); - } + downAnim = {}; + downAnim[ ref ] = refValue; - downAnim = {}; - downAnim[ ref ] = refValue; + // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here + for ( ; i < times; i++ ) { + upAnim = {}; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; - // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here - for ( ; i < times; i++ ) { - upAnim = {}; - upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + element + .animate( upAnim, speed, easing ) + .animate( downAnim, speed, easing ); - element - .animate( upAnim, speed, easing ) - .animate( downAnim, speed, easing ); + distance = hide ? distance * 2 : distance / 2; + } - distance = hide ? distance * 2 : distance / 2; - } + // Last Bounce when Hiding + if ( hide ) { + upAnim = { opacity: 0 }; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; - // Last Bounce when Hiding - if ( hide ) { - upAnim = { opacity: 0 }; - upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + element.animate( upAnim, speed, easing ); + } - element.animate( upAnim, speed, easing ); - } + element.queue( done ); - element.queue( done ); - - $.effects.unshift( element, queuelen, anims + 1 ); -} ); + $.effects.unshift( element, queuelen, anims + 1 ); + } ); -/*! - * jQuery UI Effects Clip 1.12.1 + /*! + * jQuery UI Effects Clip 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17920,43 +18271,42 @@ var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) //>>demos: http://jqueryui.com/effect/ + var effectsEffectClip = $.effects.define( "clip", "hide", function( options, done ) { + var start, + animate = {}, + element = $( this ), + direction = options.direction || "vertical", + both = direction === "both", + horizontal = both || direction === "horizontal", + vertical = both || direction === "vertical"; -var effectsEffectClip = $.effects.define( "clip", "hide", function( options, done ) { - var start, - animate = {}, - element = $( this ), - direction = options.direction || "vertical", - both = direction === "both", - horizontal = both || direction === "horizontal", - vertical = both || direction === "vertical"; + start = element.cssClip(); + animate.clip = { + top: vertical ? ( start.bottom - start.top ) / 2 : start.top, + right: horizontal ? ( start.right - start.left ) / 2 : start.right, + bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom, + left: horizontal ? ( start.right - start.left ) / 2 : start.left + }; - start = element.cssClip(); - animate.clip = { - top: vertical ? ( start.bottom - start.top ) / 2 : start.top, - right: horizontal ? ( start.right - start.left ) / 2 : start.right, - bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom, - left: horizontal ? ( start.right - start.left ) / 2 : start.left - }; + $.effects.createPlaceholder( element ); - $.effects.createPlaceholder( element ); + if ( options.mode === "show" ) { + element.cssClip( animate.clip ); + animate.clip = start; + } - if ( options.mode === "show" ) { - element.cssClip( animate.clip ); - animate.clip = start; - } + element.animate( animate, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); - element.animate( animate, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); - -} ); + } ); -/*! - * jQuery UI Effects Drop 1.12.1 + /*! + * jQuery UI Effects Drop 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17971,47 +18321,46 @@ var effectsEffectClip = $.effects.define( "clip", "hide", function( options, don //>>demos: http://jqueryui.com/effect/ + var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, done ) { -var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, done ) { + var distance, + element = $( this ), + mode = options.mode, + show = mode === "show", + direction = options.direction || "left", + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=", + oppositeMotion = ( motion === "+=" ) ? "-=" : "+=", + animation = { + opacity: 0 + }; - var distance, - element = $( this ), - mode = options.mode, - show = mode === "show", - direction = options.direction || "left", - ref = ( direction === "up" || direction === "down" ) ? "top" : "left", - motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=", - oppositeMotion = ( motion === "+=" ) ? "-=" : "+=", - animation = { - opacity: 0 - }; + $.effects.createPlaceholder( element ); - $.effects.createPlaceholder( element ); + distance = options.distance || + element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; - distance = options.distance || - element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; + animation[ ref ] = motion + distance; - animation[ ref ] = motion + distance; + if ( show ) { + element.css( animation ); - if ( show ) { - element.css( animation ); + animation[ ref ] = oppositeMotion + distance; + animation.opacity = 1; + } - animation[ ref ] = oppositeMotion + distance; - animation.opacity = 1; - } - - // Animate - element.animate( animation, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); -} ); + // Animate + element.animate( animation, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); + } ); -/*! - * jQuery UI Effects Explode 1.12.1 + /*! + * jQuery UI Effects Explode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18021,94 +18370,93 @@ var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, don //>>label: Explode Effect //>>group: Effects -// jscs:disable maximumLineLength + /* eslint-disable max-len */ //>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness. -// jscs:enable maximumLineLength + /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/explode-effect/ //>>demos: http://jqueryui.com/effect/ - -var effectsEffectExplode = $.effects.define( "explode", "hide", function( options, done ) { - - var i, j, left, top, mx, my, - rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3, - cells = rows, - element = $( this ), - mode = options.mode, - show = mode === "show", - - // Show and then visibility:hidden the element before calculating offset - offset = element.show().css( "visibility", "hidden" ).offset(), - - // Width and height of a piece - width = Math.ceil( element.outerWidth() / cells ), - height = Math.ceil( element.outerHeight() / rows ), - pieces = []; - - // Children animate complete: - function childComplete() { - pieces.push( this ); - if ( pieces.length === rows * cells ) { - animComplete(); - } - } - - // Clone the element for each row and cell. - for ( i = 0; i < rows; i++ ) { // ===> - top = offset.top + i * height; - my = i - ( rows - 1 ) / 2; - - for ( j = 0; j < cells; j++ ) { // ||| - left = offset.left + j * width; - mx = j - ( cells - 1 ) / 2; - - // Create a clone of the now hidden main element that will be absolute positioned - // within a wrapper div off the -left and -top equal to size of our pieces - element - .clone() - .appendTo( "body" ) - .wrap( "<div></div>" ) - .css( { - position: "absolute", - visibility: "visible", - left: -j * width, - top: -i * height - } ) - - // Select the wrapper - make it overflow: hidden and absolute positioned based on - // where the original was located +left and +top equal to the size of pieces - .parent() - .addClass( "ui-effects-explode" ) - .css( { - position: "absolute", - overflow: "hidden", - width: width, - height: height, - left: left + ( show ? mx * width : 0 ), - top: top + ( show ? my * height : 0 ), - opacity: show ? 0 : 1 - } ) - .animate( { - left: left + ( show ? 0 : mx * width ), - top: top + ( show ? 0 : my * height ), - opacity: show ? 1 : 0 - }, options.duration || 500, options.easing, childComplete ); - } - } - - function animComplete() { - element.css( { - visibility: "visible" - } ); - $( pieces ).remove(); - done(); - } -} ); - - -/*! - * jQuery UI Effects Fade 1.12.1 + var effectsEffectExplode = $.effects.define( "explode", "hide", function( options, done ) { + + var i, j, left, top, mx, my, + rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3, + cells = rows, + element = $( this ), + mode = options.mode, + show = mode === "show", + + // Show and then visibility:hidden the element before calculating offset + offset = element.show().css( "visibility", "hidden" ).offset(), + + // Width and height of a piece + width = Math.ceil( element.outerWidth() / cells ), + height = Math.ceil( element.outerHeight() / rows ), + pieces = []; + + // Children animate complete: + function childComplete() { + pieces.push( this ); + if ( pieces.length === rows * cells ) { + animComplete(); + } + } + + // Clone the element for each row and cell. + for ( i = 0; i < rows; i++ ) { // ===> + top = offset.top + i * height; + my = i - ( rows - 1 ) / 2; + + for ( j = 0; j < cells; j++ ) { // ||| + left = offset.left + j * width; + mx = j - ( cells - 1 ) / 2; + + // Create a clone of the now hidden main element that will be absolute positioned + // within a wrapper div off the -left and -top equal to size of our pieces + element + .clone() + .appendTo( "body" ) + .wrap( "<div></div>" ) + .css( { + position: "absolute", + visibility: "visible", + left: -j * width, + top: -i * height + } ) + + // Select the wrapper - make it overflow: hidden and absolute positioned based on + // where the original was located +left and +top equal to the size of pieces + .parent() + .addClass( "ui-effects-explode" ) + .css( { + position: "absolute", + overflow: "hidden", + width: width, + height: height, + left: left + ( show ? mx * width : 0 ), + top: top + ( show ? my * height : 0 ), + opacity: show ? 0 : 1 + } ) + .animate( { + left: left + ( show ? 0 : mx * width ), + top: top + ( show ? 0 : my * height ), + opacity: show ? 1 : 0 + }, options.duration || 500, options.easing, childComplete ); + } + } + + function animComplete() { + element.css( { + visibility: "visible" + } ); + $( pieces ).remove(); + done(); + } + } ); + + + /*! + * jQuery UI Effects Fade 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18123,25 +18471,24 @@ var effectsEffectExplode = $.effects.define( "explode", "hide", function( option //>>demos: http://jqueryui.com/effect/ + var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, done ) { + var show = options.mode === "show"; -var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, done ) { - var show = options.mode === "show"; + $( this ) + .css( "opacity", show ? 0 : 1 ) + .animate( { + opacity: show ? 1 : 0 + }, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); + } ); - $( this ) - .css( "opacity", show ? 0 : 1 ) - .animate( { - opacity: show ? 1 : 0 - }, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); -} ); - -/*! - * jQuery UI Effects Fold 1.12.1 + /*! + * jQuery UI Effects Fold 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18156,67 +18503,66 @@ var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, d //>>demos: http://jqueryui.com/effect/ + var effectsEffectFold = $.effects.define( "fold", "hide", function( options, done ) { -var effectsEffectFold = $.effects.define( "fold", "hide", function( options, done ) { - - // Create element - var element = $( this ), - mode = options.mode, - show = mode === "show", - hide = mode === "hide", - size = options.size || 15, - percent = /([0-9]+)%/.exec( size ), - horizFirst = !!options.horizFirst, - ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ], - duration = options.duration / 2, + // Create element + var element = $( this ), + mode = options.mode, + show = mode === "show", + hide = mode === "hide", + size = options.size || 15, + percent = /([0-9]+)%/.exec( size ), + horizFirst = !!options.horizFirst, + ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ], + duration = options.duration / 2, - placeholder = $.effects.createPlaceholder( element ), + placeholder = $.effects.createPlaceholder( element ), - start = element.cssClip(), - animation1 = { clip: $.extend( {}, start ) }, - animation2 = { clip: $.extend( {}, start ) }, + start = element.cssClip(), + animation1 = { clip: $.extend( {}, start ) }, + animation2 = { clip: $.extend( {}, start ) }, - distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ], + distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ], - queuelen = element.queue().length; + queuelen = element.queue().length; - if ( percent ) { - size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; - } - animation1.clip[ ref[ 0 ] ] = size; - animation2.clip[ ref[ 0 ] ] = size; - animation2.clip[ ref[ 1 ] ] = 0; + if ( percent ) { + size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; + } + animation1.clip[ ref[ 0 ] ] = size; + animation2.clip[ ref[ 0 ] ] = size; + animation2.clip[ ref[ 1 ] ] = 0; - if ( show ) { - element.cssClip( animation2.clip ); - if ( placeholder ) { - placeholder.css( $.effects.clipToBox( animation2 ) ); - } + if ( show ) { + element.cssClip( animation2.clip ); + if ( placeholder ) { + placeholder.css( $.effects.clipToBox( animation2 ) ); + } - animation2.clip = start; - } + animation2.clip = start; + } - // Animate - element - .queue( function( next ) { - if ( placeholder ) { - placeholder - .animate( $.effects.clipToBox( animation1 ), duration, options.easing ) - .animate( $.effects.clipToBox( animation2 ), duration, options.easing ); - } + // Animate + element + .queue( function( next ) { + if ( placeholder ) { + placeholder + .animate( $.effects.clipToBox( animation1 ), duration, options.easing ) + .animate( $.effects.clipToBox( animation2 ), duration, options.easing ); + } - next(); - } ) - .animate( animation1, duration, options.easing ) - .animate( animation2, duration, options.easing ) - .queue( done ); + next(); + } ) + .animate( animation1, duration, options.easing ) + .animate( animation2, duration, options.easing ) + .queue( done ); - $.effects.unshift( element, queuelen, 4 ); -} ); + $.effects.unshift( element, queuelen, 4 ); + } ); -/*! - * jQuery UI Effects Highlight 1.12.1 + /*! + * jQuery UI Effects Highlight 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18231,35 +18577,34 @@ var effectsEffectFold = $.effects.define( "fold", "hide", function( options, don //>>demos: http://jqueryui.com/effect/ + var effectsEffectHighlight = $.effects.define( "highlight", "show", function( options, done ) { + var element = $( this ), + animation = { + backgroundColor: element.css( "backgroundColor" ) + }; -var effectsEffectHighlight = $.effects.define( "highlight", "show", function( options, done ) { - var element = $( this ), - animation = { - backgroundColor: element.css( "backgroundColor" ) - }; + if ( options.mode === "hide" ) { + animation.opacity = 0; + } - if ( options.mode === "hide" ) { - animation.opacity = 0; - } + $.effects.saveStyle( element ); - $.effects.saveStyle( element ); - - element - .css( { - backgroundImage: "none", - backgroundColor: options.color || "#ffff99" - } ) - .animate( animation, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); -} ); + element + .css( { + backgroundImage: "none", + backgroundColor: options.color || "#ffff99" + } ) + .animate( animation, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); + } ); -/*! - * jQuery UI Effects Size 1.12.1 + /*! + * jQuery UI Effects Size 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18274,169 +18619,170 @@ var effectsEffectHighlight = $.effects.define( "highlight", "show", function( op //>>demos: http://jqueryui.com/effect/ - -var effectsEffectSize = $.effects.define( "size", function( options, done ) { - - // Create element - var baseline, factor, temp, - element = $( this ), - - // Copy for children - cProps = [ "fontSize" ], - vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], - hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], - - // Set options - mode = options.mode, - restore = mode !== "effect", - scale = options.scale || "both", - origin = options.origin || [ "middle", "center" ], - position = element.css( "position" ), - pos = element.position(), - original = $.effects.scaledDimensions( element ), - from = options.from || original, - to = options.to || $.effects.scaledDimensions( element, 0 ); - - $.effects.createPlaceholder( element ); - - if ( mode === "show" ) { - temp = from; - from = to; - to = temp; - } - - // Set scaling factor - factor = { - from: { - y: from.height / original.height, - x: from.width / original.width - }, - to: { - y: to.height / original.height, - x: to.width / original.width - } - }; - - // Scale the css box - if ( scale === "box" || scale === "both" ) { - - // Vertical props scaling - if ( factor.from.y !== factor.to.y ) { - from = $.effects.setTransition( element, vProps, factor.from.y, from ); - to = $.effects.setTransition( element, vProps, factor.to.y, to ); - } - - // Horizontal props scaling - if ( factor.from.x !== factor.to.x ) { - from = $.effects.setTransition( element, hProps, factor.from.x, from ); - to = $.effects.setTransition( element, hProps, factor.to.x, to ); - } - } - - // Scale the content - if ( scale === "content" || scale === "both" ) { - - // Vertical props scaling - if ( factor.from.y !== factor.to.y ) { - from = $.effects.setTransition( element, cProps, factor.from.y, from ); - to = $.effects.setTransition( element, cProps, factor.to.y, to ); - } - } - - // Adjust the position properties based on the provided origin points - if ( origin ) { - baseline = $.effects.getBaseline( origin, original ); - from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top; - from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left; - to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; - to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; - } - element.css( from ); - - // Animate the children if desired - if ( scale === "content" || scale === "both" ) { - - vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps ); - hProps = hProps.concat( [ "marginLeft", "marginRight" ] ); - - // Only animate children with width attributes specified - // TODO: is this right? should we include anything with css width specified as well - element.find( "*[width]" ).each( function() { - var child = $( this ), - childOriginal = $.effects.scaledDimensions( child ), - childFrom = { - height: childOriginal.height * factor.from.y, - width: childOriginal.width * factor.from.x, - outerHeight: childOriginal.outerHeight * factor.from.y, - outerWidth: childOriginal.outerWidth * factor.from.x - }, - childTo = { - height: childOriginal.height * factor.to.y, - width: childOriginal.width * factor.to.x, - outerHeight: childOriginal.height * factor.to.y, - outerWidth: childOriginal.width * factor.to.x - }; - - // Vertical props scaling - if ( factor.from.y !== factor.to.y ) { - childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom ); - childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo ); - } - - // Horizontal props scaling - if ( factor.from.x !== factor.to.x ) { - childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom ); - childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo ); - } - - if ( restore ) { - $.effects.saveStyle( child ); - } - - // Animate children - child.css( childFrom ); - child.animate( childTo, options.duration, options.easing, function() { - - // Restore children - if ( restore ) { - $.effects.restoreStyle( child ); - } - } ); - } ); - } - - // Animate - element.animate( to, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: function() { - - var offset = element.offset(); - - if ( to.opacity === 0 ) { - element.css( "opacity", from.opacity ); - } - - if ( !restore ) { - element - .css( "position", position === "static" ? "relative" : position ) - .offset( offset ); - - // Need to save style here so that automatic style restoration - // doesn't restore to the original styles from before the animation. - $.effects.saveStyle( element ); - } - - done(); - } - } ); - -} ); - - -/*! - * jQuery UI Effects Scale 1.12.1 + var effectsEffectSize = $.effects.define( "size", function( options, done ) { + + // Create element + var baseline, factor, temp, + element = $( this ), + + // Copy for children + cProps = [ "fontSize" ], + vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], + hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], + + // Set options + mode = options.mode, + restore = mode !== "effect", + scale = options.scale || "both", + origin = options.origin || [ "middle", "center" ], + position = element.css( "position" ), + pos = element.position(), + original = $.effects.scaledDimensions( element ), + from = options.from || original, + to = options.to || $.effects.scaledDimensions( element, 0 ); + + $.effects.createPlaceholder( element ); + + if ( mode === "show" ) { + temp = from; + from = to; + to = temp; + } + + // Set scaling factor + factor = { + from: { + y: from.height / original.height, + x: from.width / original.width + }, + to: { + y: to.height / original.height, + x: to.width / original.width + } + }; + + // Scale the css box + if ( scale === "box" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + from = $.effects.setTransition( element, vProps, factor.from.y, from ); + to = $.effects.setTransition( element, vProps, factor.to.y, to ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + from = $.effects.setTransition( element, hProps, factor.from.x, from ); + to = $.effects.setTransition( element, hProps, factor.to.x, to ); + } + } + + // Scale the content + if ( scale === "content" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + from = $.effects.setTransition( element, cProps, factor.from.y, from ); + to = $.effects.setTransition( element, cProps, factor.to.y, to ); + } + } + + // Adjust the position properties based on the provided origin points + if ( origin ) { + baseline = $.effects.getBaseline( origin, original ); + from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top; + from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left; + to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; + to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; + } + delete from.outerHeight; + delete from.outerWidth; + element.css( from ); + + // Animate the children if desired + if ( scale === "content" || scale === "both" ) { + + vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps ); + hProps = hProps.concat( [ "marginLeft", "marginRight" ] ); + + // Only animate children with width attributes specified + // TODO: is this right? should we include anything with css width specified as well + element.find( "*[width]" ).each( function() { + var child = $( this ), + childOriginal = $.effects.scaledDimensions( child ), + childFrom = { + height: childOriginal.height * factor.from.y, + width: childOriginal.width * factor.from.x, + outerHeight: childOriginal.outerHeight * factor.from.y, + outerWidth: childOriginal.outerWidth * factor.from.x + }, + childTo = { + height: childOriginal.height * factor.to.y, + width: childOriginal.width * factor.to.x, + outerHeight: childOriginal.height * factor.to.y, + outerWidth: childOriginal.width * factor.to.x + }; + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom ); + childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom ); + childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo ); + } + + if ( restore ) { + $.effects.saveStyle( child ); + } + + // Animate children + child.css( childFrom ); + child.animate( childTo, options.duration, options.easing, function() { + + // Restore children + if ( restore ) { + $.effects.restoreStyle( child ); + } + } ); + } ); + } + + // Animate + element.animate( to, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: function() { + + var offset = element.offset(); + + if ( to.opacity === 0 ) { + element.css( "opacity", from.opacity ); + } + + if ( !restore ) { + element + .css( "position", position === "static" ? "relative" : position ) + .offset( offset ); + + // Need to save style here so that automatic style restoration + // doesn't restore to the original styles from before the animation. + $.effects.saveStyle( element ); + } + + done(); + } + } ); + + } ); + + + /*! + * jQuery UI Effects Scale 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18451,33 +18797,32 @@ var effectsEffectSize = $.effects.define( "size", function( options, done ) { //>>demos: http://jqueryui.com/effect/ + var effectsEffectScale = $.effects.define( "scale", function( options, done ) { -var effectsEffectScale = $.effects.define( "scale", function( options, done ) { + // Create element + var el = $( this ), + mode = options.mode, + percent = parseInt( options.percent, 10 ) || + ( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ), - // Create element - var el = $( this ), - mode = options.mode, - percent = parseInt( options.percent, 10 ) || - ( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ), + newOptions = $.extend( true, { + from: $.effects.scaledDimensions( el ), + to: $.effects.scaledDimensions( el, percent, options.direction || "both" ), + origin: options.origin || [ "middle", "center" ] + }, options ); - newOptions = $.extend( true, { - from: $.effects.scaledDimensions( el ), - to: $.effects.scaledDimensions( el, percent, options.direction || "both" ), - origin: options.origin || [ "middle", "center" ] - }, options ); + // Fade option to support puff + if ( options.fade ) { + newOptions.from.opacity = 1; + newOptions.to.opacity = 0; + } - // Fade option to support puff - if ( options.fade ) { - newOptions.from.opacity = 1; - newOptions.to.opacity = 0; - } - - $.effects.effect.size.call( this, newOptions, done ); -} ); + $.effects.effect.size.call( this, newOptions, done ); + } ); -/*! - * jQuery UI Effects Puff 1.12.1 + /*! + * jQuery UI Effects Puff 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18492,19 +18837,18 @@ var effectsEffectScale = $.effects.define( "scale", function( options, done ) { //>>demos: http://jqueryui.com/effect/ + var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, done ) { + var newOptions = $.extend( true, {}, options, { + fade: true, + percent: parseInt( options.percent, 10 ) || 150 + } ); -var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, done ) { - var newOptions = $.extend( true, {}, options, { - fade: true, - percent: parseInt( options.percent, 10 ) || 150 - } ); - - $.effects.effect.scale.call( this, newOptions, done ); -} ); + $.effects.effect.scale.call( this, newOptions, done ); + } ); -/*! - * jQuery UI Effects Pulsate 1.12.1 + /*! + * jQuery UI Effects Pulsate 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18519,42 +18863,41 @@ var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, don //>>demos: http://jqueryui.com/effect/ + var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( options, done ) { + var element = $( this ), + mode = options.mode, + show = mode === "show", + hide = mode === "hide", + showhide = show || hide, -var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( options, done ) { - var element = $( this ), - mode = options.mode, - show = mode === "show", - hide = mode === "hide", - showhide = show || hide, + // Showing or hiding leaves off the "last" animation + anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), + duration = options.duration / anims, + animateTo = 0, + i = 1, + queuelen = element.queue().length; - // Showing or hiding leaves off the "last" animation - anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), - duration = options.duration / anims, - animateTo = 0, - i = 1, - queuelen = element.queue().length; + if ( show || !element.is( ":visible" ) ) { + element.css( "opacity", 0 ).show(); + animateTo = 1; + } - if ( show || !element.is( ":visible" ) ) { - element.css( "opacity", 0 ).show(); - animateTo = 1; - } + // Anims - 1 opacity "toggles" + for ( ; i < anims; i++ ) { + element.animate( { opacity: animateTo }, duration, options.easing ); + animateTo = 1 - animateTo; + } - // Anims - 1 opacity "toggles" - for ( ; i < anims; i++ ) { - element.animate( { opacity: animateTo }, duration, options.easing ); - animateTo = 1 - animateTo; - } + element.animate( { opacity: animateTo }, duration, options.easing ); - element.animate( { opacity: animateTo }, duration, options.easing ); + element.queue( done ); - element.queue( done ); - - $.effects.unshift( element, queuelen, anims + 1 ); -} ); + $.effects.unshift( element, queuelen, anims + 1 ); + } ); -/*! - * jQuery UI Effects Shake 1.12.1 + /*! + * jQuery UI Effects Shake 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18569,52 +18912,51 @@ var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( option //>>demos: http://jqueryui.com/effect/ + var effectsEffectShake = $.effects.define( "shake", function( options, done ) { -var effectsEffectShake = $.effects.define( "shake", function( options, done ) { - - var i = 1, - element = $( this ), - direction = options.direction || "left", - distance = options.distance || 20, - times = options.times || 3, - anims = times * 2 + 1, - speed = Math.round( options.duration / anims ), - ref = ( direction === "up" || direction === "down" ) ? "top" : "left", - positiveMotion = ( direction === "up" || direction === "left" ), - animation = {}, - animation1 = {}, - animation2 = {}, + var i = 1, + element = $( this ), + direction = options.direction || "left", + distance = options.distance || 20, + times = options.times || 3, + anims = times * 2 + 1, + speed = Math.round( options.duration / anims ), + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + positiveMotion = ( direction === "up" || direction === "left" ), + animation = {}, + animation1 = {}, + animation2 = {}, - queuelen = element.queue().length; + queuelen = element.queue().length; - $.effects.createPlaceholder( element ); + $.effects.createPlaceholder( element ); - // Animation - animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; - animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; - animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; + // Animation + animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; + animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; + animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; - // Animate - element.animate( animation, speed, options.easing ); + // Animate + element.animate( animation, speed, options.easing ); - // Shakes - for ( ; i < times; i++ ) { - element - .animate( animation1, speed, options.easing ) - .animate( animation2, speed, options.easing ); - } + // Shakes + for ( ; i < times; i++ ) { + element + .animate( animation1, speed, options.easing ) + .animate( animation2, speed, options.easing ); + } - element - .animate( animation1, speed, options.easing ) - .animate( animation, speed / 2, options.easing ) - .queue( done ); + element + .animate( animation1, speed, options.easing ) + .animate( animation, speed / 2, options.easing ) + .queue( done ); - $.effects.unshift( element, queuelen, anims + 1 ); -} ); + $.effects.unshift( element, queuelen, anims + 1 ); + } ); -/*! - * jQuery UI Effects Slide 1.12.1 + /*! + * jQuery UI Effects Slide 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18629,54 +18971,53 @@ var effectsEffectShake = $.effects.define( "shake", function( options, done ) { //>>demos: http://jqueryui.com/effect/ - -var effectsEffectSlide = $.effects.define( "slide", "show", function( options, done ) { - var startClip, startRef, - element = $( this ), - map = { - up: [ "bottom", "top" ], - down: [ "top", "bottom" ], - left: [ "right", "left" ], - right: [ "left", "right" ] - }, - mode = options.mode, - direction = options.direction || "left", - ref = ( direction === "up" || direction === "down" ) ? "top" : "left", - positiveMotion = ( direction === "up" || direction === "left" ), - distance = options.distance || - element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ), - animation = {}; - - $.effects.createPlaceholder( element ); - - startClip = element.cssClip(); - startRef = element.position()[ ref ]; - - // Define hide animation - animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef; - animation.clip = element.cssClip(); - animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ]; - - // Reverse the animation if we're showing - if ( mode === "show" ) { - element.cssClip( animation.clip ); - element.css( ref, animation[ ref ] ); - animation.clip = startClip; - animation[ ref ] = startRef; - } - - // Actually animate - element.animate( animation, { - queue: false, - duration: options.duration, - easing: options.easing, - complete: done - } ); -} ); - - -/*! - * jQuery UI Effects Transfer 1.12.1 + var effectsEffectSlide = $.effects.define( "slide", "show", function( options, done ) { + var startClip, startRef, + element = $( this ), + map = { + up: [ "bottom", "top" ], + down: [ "top", "bottom" ], + left: [ "right", "left" ], + right: [ "left", "right" ] + }, + mode = options.mode, + direction = options.direction || "left", + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + positiveMotion = ( direction === "up" || direction === "left" ), + distance = options.distance || + element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ), + animation = {}; + + $.effects.createPlaceholder( element ); + + startClip = element.cssClip(); + startRef = element.position()[ ref ]; + + // Define hide animation + animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef; + animation.clip = element.cssClip(); + animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ]; + + // Reverse the animation if we're showing + if ( mode === "show" ) { + element.cssClip( animation.clip ); + element.css( ref, animation[ ref ] ); + animation.clip = startClip; + animation[ ref ] = startRef; + } + + // Actually animate + element.animate( animation, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + } ); + } ); + + + /*! + * jQuery UI Effects Transfer 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18691,16 +19032,15 @@ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, d //>>demos: http://jqueryui.com/effect/ - -var effect; -if ( $.uiBackCompat !== false ) { - effect = $.effects.define( "transfer", function( options, done ) { - $( this ).transfer( options, done ); - } ); -} -var effectsEffectTransfer = effect; + var effect; + if ( $.uiBackCompat !== false ) { + effect = $.effects.define( "transfer", function( options, done ) { + $( this ).transfer( options, done ); + } ); + } + var effectsEffectTransfer = effect; -})); \ No newline at end of file +} ); diff --git a/lib/web/jquery/jquery.tabs.js b/lib/web/jquery/jquery.tabs.js index d6cf21d6d5dd8..e47fc81b37110 100644 --- a/lib/web/jquery/jquery.tabs.js +++ b/lib/web/jquery/jquery.tabs.js @@ -1,300 +1,12 @@ -/* ======================================================== - * bootstrap-tab.js v2.0.4 - * http://twitter.github.com/bootstrap/javascript.html#tabs - * ======================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================== */ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ define([ - "jquery" -], function(jQuery){ + "jquery", + "jquery/bootstrap/tab", + "jquery/bootstrap/collapse", +], function () { - -!function ($) { - - "use strict"; // jshint ;_; - - - /* TAB CLASS DEFINITION - * ==================== */ - - var Tab = function ( element ) { - this.element = $(element) - }; - - Tab.prototype = { - - constructor: Tab - - , show: function () { - var $this = this.element - , $ul = $this.closest('ul:not(.dropdown-menu)') - , selector = $this.attr('data-target') - , previous - , $target - , e; - - if (!selector) { - selector = $this.attr('href'); - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7 - } - - if ( $this.parent('li').hasClass('active') ) return; - - previous = $ul.find('.active a').last()[0]; - - e = $.Event('show', { - relatedTarget: previous - }); - - $this.trigger(e); - - if (e.isDefaultPrevented()) return; - - $target = $(selector); - - this.activate($this.parent('li'), $ul); - this.activate($target, $target.parent(), function () { - $this.trigger({ - type: 'shown' - , relatedTarget: previous - }) - }) - } - - , activate: function ( element, container, callback) { - var $active = container.find('> .active') - , transition = callback - && $.support.transition - && $active.hasClass('fade'); - - function next() { - $active - .removeClass('active') - .find('> .dropdown-menu > .active') - .removeClass('active'); - - element.addClass('active'); - - if (transition) { - element[0].offsetWidth; // reflow for transition - element.addClass('in'); - } else { - element.removeClass('fade') - } - - if ( element.parent('.dropdown-menu') ) { - element.closest('li.dropdown').addClass('active') - } - - callback && callback() - } - - transition ? - $active.one($.support.transition.end, next) : - next(); - - $active.removeClass('in') - } - }; - - - /* TAB PLUGIN DEFINITION - * ===================== */ - - $.fn.tab = function ( option ) { - return this.each(function () { - var $this = $(this) - , data = $this.data('tab'); - if (!data) $this.data('tab', (data = new Tab(this))); - if (typeof option == 'string') data[option]() - }) - }; - - $.fn.tab.Constructor = Tab; - - - /* TAB DATA-API - * ============ */ - - $(function () { - $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { - e.preventDefault(); - $(this).tab('show') - }) - }) - -}(jQuery); - -/* ============================================================= - * bootstrap-collapse.js v2.0.4 - * http://twitter.github.com/bootstrap/javascript.html#collapse - * ============================================================= - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* COLLAPSE PUBLIC CLASS DEFINITION - * ================================ */ - - var Collapse = function (element, options) { - this.$element = $(element); - this.options = $.extend({}, $.fn.collapse.defaults, options); - - if (this.options.parent) { - this.$parent = $(this.options.parent) - } - - this.options.toggle && this.toggle() - }; - - Collapse.prototype = { - - constructor: Collapse - - , dimension: function () { - var hasWidth = this.$element.hasClass('width'); - return hasWidth ? 'width' : 'height' - } - - , show: function () { - var dimension - , scroll - , actives - , hasData; - - if (this.transitioning) return; - - dimension = this.dimension(); - scroll = $.camelCase(['scroll', dimension].join('-')); - actives = this.$parent && this.$parent.find('> .accordion-group > .in'); - - if (actives && actives.length) { - hasData = actives.data('collapse'); - if (hasData && hasData.transitioning) return; - actives.collapse('hide'); - hasData || actives.data('collapse', null) - } - - this.$element[dimension](0); - this.transition('addClass', $.Event('show'), 'shown'); - this.$element[dimension](this.$element[0][scroll]); - } - - , hide: function () { - var dimension; - if (this.transitioning) return; - dimension = this.dimension(); - this.reset(this.$element[dimension]()); - this.transition('removeClass', $.Event('hide'), 'hidden'); - this.$element[dimension](0) - } - - , reset: function (size) { - var dimension = this.dimension(); - - this.$element - .removeClass('collapse') - [dimension](size || 'auto') - [0].offsetWidth; - - this.$element[size !== null ? 'addClass' : 'removeClass']('collapse'); - - return this - } - - , transition: function (method, startEvent, completeEvent) { - var that = this - , complete = function () { - if (startEvent.type == 'show') that.reset(); - that.transitioning = 0; - that.$element.trigger(completeEvent) - }; - - this.$element.trigger(startEvent); - - if (startEvent.isDefaultPrevented()) return; - - this.transitioning = 1; - - this.$element[method]('in'); - - $.support.transition && this.$element.hasClass('collapse') ? - this.$element.one($.support.transition.end, complete) : - complete() - } - - , toggle: function () { - this[this.$element.hasClass('in') ? 'hide' : 'show'](); - } - - }; - - - /* COLLAPSIBLE PLUGIN DEFINITION - * ============================== */ - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('collapse') - , options = typeof option == 'object' && option; - if (!data) $this.data('collapse', (data = new Collapse(this, options))); - if (typeof option == 'string') data[option]() - }) - }; - - $.fn.collapse.defaults = { - toggle: true - }; - - $.fn.collapse.Constructor = Collapse; - - - /* COLLAPSIBLE DATA-API - * ==================== */ - - $(function () { - $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { - var $this = $(this), href - , target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - , option = $(target).data('collapse') ? 'toggle' : $this.data(); - $(target).collapse(option); - $(this).toggleClass('active'); - }) - }) - -}(jQuery); - -}); \ No newline at end of file +}); diff --git a/lib/web/jquery/patches/jquery-ui-sortable.js b/lib/web/jquery/patches/jquery-ui-sortable.js new file mode 100644 index 0000000000000..07884e2339bae --- /dev/null +++ b/lib/web/jquery/patches/jquery-ui-sortable.js @@ -0,0 +1,298 @@ +/*! + * jQuery UI Sortable + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + /** + * Patch for sortable widget. + * Can safely remove only when jQuery UI is upgraded to >= 1.13.1. + * Fixes: + * https://github.com/jquery/jquery-ui/pull/2008 + * https://github.com/jquery/jquery-ui/pull/2009 + */ + var sortablePatch = { + /** @inheritdoc */ + _mouseDrag: function (event) { + var i, item, itemElement, intersection, + o = this.options; + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + //Set the helper position + if (!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left + "px"; + } + if (!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top + "px"; + } + + //Do scrolling + if (o.scroll) { + if (this._scroll(event) !== false) { + + //Update item positions used in position checks + this._refreshItemPositions(true); + + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + } + } + + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + + //Rearrange + for (i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + item = this.items[i]; + itemElement = item.item[0]; + intersection = this._intersectsWithPointer(item); + if (!intersection) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items from other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this, moving items in "sub-sortables" can cause + // the placeholder to jitter between the outer and inner container. + if (item.instance !== this.currentContainer) { + continue; + } + + // Cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if (itemElement !== this.currentItem[0] && + this.placeholder[intersection === 1 ? + "next" : "prev"]()[0] !== itemElement && + !$.contains(this.placeholder[0], itemElement) && + (this.options.type === "semi-dynamic" ? + !$.contains(this.element[0], itemElement) : + true + ) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if (this.options.tolerance === "pointer" || + this._intersectsWithSides(item)) { + this._rearrange(event, item); + } else { + break; + } + + this._trigger("change", event, this._uiHash()); + break; + } + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if ($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + //Call callbacks + this._trigger("sort", event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + /** @inheritdoc */ + refreshPositions: function (fast) { + + // Determine whether items are being displayed horizontally + this.floating = this.items.length ? + this.options.axis === "x" || this._isFloating(this.items[0].item) : + false; + + // This has to be redone because due to the item being moved out/into the offsetParent, + // the offsetParent's position will change + if (this.offsetParent && this.helper) { + this.offset.parent = this._getParentOffset(); + } + + this._refreshItemPositions(fast); + + var i, p; + + if (this.options.custom && this.options.custom.refreshContainers) { + this.options.custom.refreshContainers.call(this); + } else { + for (i = this.containers.length - 1; i >= 0; i--) { + p = this.containers[i].element.offset(); + this.containers[i].containerCache.left = p.left; + this.containers[i].containerCache.top = p.top; + this.containers[i].containerCache.width = + this.containers[i].element.outerWidth(); + this.containers[i].containerCache.height = + this.containers[i].element.outerHeight(); + } + } + + return this; + }, + + /** @inheritdoc */ + _contactContainers: function (event) { + var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, + floating, axis, + innermostContainer = null, + innermostIndex = null; + + // Get innermost container that intersects with item + for (i = this.containers.length - 1; i >= 0; i--) { + + // Never consider a container that's located within the item itself + if ($.contains(this.currentItem[0], this.containers[i].element[0])) { + continue; + } + + if (this._intersectsWith(this.containers[i].containerCache)) { + + // If we've already found a container and it's more "inner" than this, then continue + if (innermostContainer && + $.contains( + this.containers[i].element[0], + innermostContainer.element[0])) { + continue; + } + + innermostContainer = this.containers[i]; + innermostIndex = i; + + } else { + + // container doesn't intersect. trigger "out" event if necessary + if (this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", event, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + // If no intersecting containers found, return + if (!innermostContainer) { + return; + } + + // Move the item into the container if it's not there already + if (this.containers.length === 1) { + if (!this.containers[innermostIndex].containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + } else { + + // When entering a new container, we will find the item with the least distance and + // append our item near it + dist = 10000; + itemWithLeastDistance = null; + floating = innermostContainer.floating || this._isFloating(this.currentItem); + posProperty = floating ? "left" : "top"; + sizeProperty = floating ? "width" : "height"; + axis = floating ? "pageX" : "pageY"; + + for (j = this.items.length - 1; j >= 0; j--) { + if (!$.contains( + this.containers[innermostIndex].element[0], this.items[j].item[0]) + ) { + continue; + } + if (this.items[j].item[0] === this.currentItem[0]) { + continue; + } + + cur = this.items[j].item.offset()[posProperty]; + nearBottom = false; + if (event[axis] - cur > this.items[j][sizeProperty] / 2) { + nearBottom = true; + } + + if (Math.abs(event[axis] - cur) < dist) { + dist = Math.abs(event[axis] - cur); + itemWithLeastDistance = this.items[j]; + this.direction = nearBottom ? "up" : "down"; + } + } + + //Check if dropOnEmpty is enabled + if (!itemWithLeastDistance && !this.options.dropOnEmpty) { + return; + } + + if (this.currentContainer === this.containers[innermostIndex]) { + if (!this.currentContainer.containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash()); + this.currentContainer.containerCache.over = 1; + } + return; + } + + if (itemWithLeastDistance) { + this._rearrange(event, itemWithLeastDistance, null, true); + } else { + this._rearrange(event, null, this.containers[innermostIndex].element, true); + } + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + this.currentContainer = this.containers[innermostIndex]; + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + //Update scrollParent + this.scrollParent = this.placeholder.scrollParent(); + + //Update overflowOffset + if (this.scrollParent[0] !== this.document[0] && + this.scrollParent[0].tagName !== "HTML") { + this.overflowOffset = this.scrollParent.offset(); + } + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + + } + } + + return function () { + var majorVersion = parseInt($.ui.version.split('.')[0]), + minorVersion = parseInt($.ui.version.split('.')[1]), + patchVersion = parseInt($.ui.version.split('.')[2]) + + if (majorVersion === 1 && minorVersion === 13 && patchVersion > 0 || + majorVersion === 1 && minorVersion >= 14 || + majorVersion >= 2 + ) { + console.warn('jQuery ui sortable patch is no longer necessary, and should be removed'); + } + + $.widget('ui.sortable', $.ui.sortable, sortablePatch); + }; + +}); diff --git a/lib/web/jquery/timepicker.js b/lib/web/jquery/timepicker.js index a488e46f56665..4f4aebefaa03e 100644 --- a/lib/web/jquery/timepicker.js +++ b/lib/web/jquery/timepicker.js @@ -1,13 +1,9 @@ -/*! jQuery Timepicker Addon - v1.4.3 - 2013-11-30 +/*! jQuery Timepicker Addon - v1.6.3 - 2016-04-20 * http://trentrichardson.com/examples/timepicker -* Copyright (c) 2013 Trent Richardson; Licensed MIT */ +* Copyright (c) 2016 Trent Richardson; Licensed MIT */ (function (factory) { if (typeof define === 'function' && define.amd) { - define([ - "jquery", - "jquery-ui-modules/datepicker", - "jquery-ui-modules/slider" - ], factory); + define(['jquery', 'jquery-ui-modules/datepicker', 'jquery-ui-modules/slider'], factory); } else { factory(jQuery); } @@ -26,7 +22,7 @@ */ $.extend($.ui, { timepicker: { - version: "1.4.3" + version: "1.6.3" } }); @@ -58,6 +54,7 @@ this._defaults = { // Global defaults for all the datetime picker instances showButtonPanel: true, timeOnly: false, + timeOnlyShowDate: false, showHour: null, showMinute: null, showSecond: null, @@ -88,6 +85,8 @@ microsecMax: 999, minDateTime: null, maxDateTime: null, + maxTime: null, + minTime: null, onSelect: null, hourGrid: 0, minuteGrid: 0, @@ -100,6 +99,7 @@ altTimeFormat: null, altSeparator: null, altTimeSuffix: null, + altRedirectFocus: true, pickerTimeFormat: null, pickerTimeSuffix: null, showTimepicker: true, @@ -107,8 +107,10 @@ addSliderAccess: false, sliderAccessArgs: null, controlType: 'slider', + oneLine: false, defaultValue: null, - parse: 'strict' + parse: 'strict', + afterInject: null }; $.extend(this._defaults, this.regional['']); }; @@ -124,6 +126,8 @@ millisec_slider: null, microsec_slider: null, timezone_select: null, + maxTime: null, + minTime: null, hour: 0, minute: 0, second: 0, @@ -189,7 +193,7 @@ }, onChangeMonthYear: function (year, month, dp_inst) { // Update the time as well : this prevents the time from disappearing from the $input field. - tp_inst._updateDateTime(dp_inst); + // tp_inst._updateDateTime(dp_inst); if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); } @@ -205,7 +209,7 @@ }; for (i in overrides) { if (overrides.hasOwnProperty(i)) { - fns[i] = opts[i] || null; + fns[i] = opts[i] || this._defaults[i] || null; } } @@ -227,8 +231,8 @@ (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); // controlType is string - key to our this._controls - if (typeof (tp_inst._defaults.controlType) === 'string') { - if (tp_inst._defaults.controlType === 'slider' && typeof ($.ui.slider) === 'undefined') { + if (typeof(tp_inst._defaults.controlType) === 'string') { + if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { tp_inst._defaults.controlType = 'select'; } tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; @@ -248,10 +252,7 @@ if (tzl > 0 && typeof timezoneList[0] !== 'object') { for (; tzi < tzl; tzi++) { tzv = timezoneList[tzi]; - timezoneList[tzi] = { - value: tzv, - label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) - }; + timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; } } tp_inst._defaults.timezoneList = timezoneList; @@ -273,11 +274,14 @@ tp_inst.$input = $input; if (tp_inst._defaults.altField) { - tp_inst.$altInput = $(tp_inst._defaults.altField).css({ - cursor: 'pointer' - }).focus(function () { - $input.trigger("focus"); - }); + tp_inst.$altInput = $(tp_inst._defaults.altField); + if (tp_inst._defaults.altRedirectFocus === true) { + tp_inst.$altInput.css({ + cursor: 'pointer' + }).focus(function () { + $input.trigger("focus"); + }); + } } if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { @@ -311,11 +315,12 @@ * add our sliders to the calendar */ _addTimePicker: function (dp_inst) { - var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val(); + var currDT = $.trim((this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val()); this.timeDefined = this._parseTime(currDT); this._limitMinMaxDateTime(dp_inst, false); this._injectTimePicker(); + this._afterInject(); }, /* @@ -352,6 +357,16 @@ } }, + /* + * Handle callback option after injecting timepicker + */ + _afterInject: function() { + var o = this.inst.settings; + if ($.isFunction(o.afterInject)) { + o.afterInject.call(this); + } + }, + /* * generate and inject html for timepicker into ui datepicker */ @@ -370,9 +385,9 @@ // Prevent displaying twice if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { - var noDisplay = ' style="display:none;"', - html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + - '<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>'; + var noDisplay = ' ui_tpicker_unit_hide', + html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + (o.oneLine && o.controlType === 'select' ? ' ui-timepicker-oneLine' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label' + ((o.showTime) ? '' : noDisplay) + '">' + o.timeText + '</dt>' + + '<dd class="ui_tpicker_time '+ ((o.showTime) ? '' : noDisplay) + '"><input class="ui_tpicker_time_input" ' + (o.timeInput ? '' : 'disabled') + '/></dd>'; // Create the markup for (i = 0, l = this.units.length; i < l; i++) { @@ -386,8 +401,8 @@ max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); gridSize[litem] = 0; - html += '<dt class="ui_tpicker_' + litem + '_label"' + (show ? '' : noDisplay) + '>' + o[litem + 'Text'] + '</dt>' + - '<dd class="ui_tpicker_' + litem + '"><div class="ui_tpicker_' + litem + '_slider"' + (show ? '' : noDisplay) + '></div>'; + html += '<dt class="ui_tpicker_' + litem + '_label' + (show ? '' : noDisplay) + '">' + o[litem + 'Text'] + '</dt>' + + '<dd class="ui_tpicker_' + litem + (show ? '' : noDisplay) + '"><div class="ui_tpicker_' + litem + '_slider' + (show ? '' : noDisplay) + '"></div>'; if (show && o[litem + 'Grid'] > 0) { html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; @@ -398,7 +413,8 @@ var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); html += '<td data-for="' + litem + '">' + tmph + '</td>'; } - } else { + } + else { for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { gridSize[litem]++; html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>'; @@ -412,8 +428,8 @@ // Timezone var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; - html += '<dt class="ui_tpicker_timezone_label"' + (showTz ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; - html += '<dd class="ui_tpicker_timezone" ' + (showTz ? '' : noDisplay) + '></dd>'; + html += '<dt class="ui_tpicker_timezone_label' + (showTz ? '' : noDisplay) + '">' + o.timezoneText + '</dt>'; + html += '<dd class="ui_tpicker_timezone' + (showTz ? '' : noDisplay) + '"></dd>'; // Create the elements from string html += '</dl></div>'; @@ -452,7 +468,8 @@ if (f === 'hour') { if (ap.indexOf('p') !== -1 && n < 12) { n += 12; - } else { + } + else { if (ap.indexOf('a') !== -1 && n === 12) { n = 0; } @@ -478,7 +495,7 @@ $.map(o.timezoneList, function (val, idx) { return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val); })); - if (typeof (this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") { + if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") { var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1; if (local_timezone === this.timezone) { selectLocalTimezone(tp_inst); @@ -486,7 +503,7 @@ this.timezone_select.val(this.timezone); } } else { - if (typeof (this.hour) !== "undefined" && this.hour !== null && this.hour !== "") { + if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") { this.timezone_select.val(o.timezone); } else { selectLocalTimezone(tp_inst); @@ -495,6 +512,7 @@ this.timezone_select.change(function () { tp_inst._onTimeChange(); tp_inst._onSelectHandler(); + tp_inst._afterInject(); }); // End timezone options @@ -506,7 +524,21 @@ $dp.append($tp); } - this.$timeObj = $tp.find('.ui_tpicker_time'); + this.$timeObj = $tp.find('.ui_tpicker_time_input'); + this.$timeObj.change(function () { + var timeFormat = tp_inst.inst.settings.timeFormat; + var parsedTime = $.datepicker.parseTime(timeFormat, this.value); + var update = new Date(); + if (parsedTime) { + update.setHours(parsedTime.hour); + update.setMinutes(parsedTime.minute); + update.setSeconds(parsedTime.second); + $.datepicker._setTime(tp_inst.inst, update); + } else { + this.value = tp_inst.formattedTime; + this.blur(); + } + }); if (this.inst !== null) { var timeDefined = this.timeDefined; @@ -533,7 +565,7 @@ oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''), newWidth = oldWidth - sliderAccessWidth, newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%', - css = {width: newWidth, marginRight: 0, marginLeft: 0}; + css = { width: newWidth, marginRight: 0, marginLeft: 0 }; css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft; $g.css(css); }); @@ -671,6 +703,44 @@ } } + if (dp_inst.settings.minTime!==null) { + var tempMinTime=new Date("01/01/1970 " + dp_inst.settings.minTime); + if (this.hour<tempMinTime.getHours()) { + this.hour=this._defaults.hourMin=tempMinTime.getHours(); + this.minute=this._defaults.minuteMin=tempMinTime.getMinutes(); + } else if (this.hour===tempMinTime.getHours() && this.minute<tempMinTime.getMinutes()) { + this.minute=this._defaults.minuteMin=tempMinTime.getMinutes(); + } else { + if (this._defaults.hourMin<tempMinTime.getHours()) { + this._defaults.hourMin=tempMinTime.getHours(); + this._defaults.minuteMin=tempMinTime.getMinutes(); + } else if (this._defaults.hourMin===tempMinTime.getHours()===this.hour && this._defaults.minuteMin<tempMinTime.getMinutes()) { + this._defaults.minuteMin=tempMinTime.getMinutes(); + } else { + this._defaults.minuteMin=0; + } + } + } + + if (dp_inst.settings.maxTime!==null) { + var tempMaxTime=new Date("01/01/1970 " + dp_inst.settings.maxTime); + if (this.hour>tempMaxTime.getHours()) { + this.hour=this._defaults.hourMax=tempMaxTime.getHours(); + this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else if (this.hour===tempMaxTime.getHours() && this.minute>tempMaxTime.getMinutes()) { + this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else { + if (this._defaults.hourMax>tempMaxTime.getHours()) { + this._defaults.hourMax=tempMaxTime.getHours(); + this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else if (this._defaults.hourMax===tempMaxTime.getHours()===this.hour && this._defaults.minuteMax>tempMaxTime.getMinutes()) { + this._defaults.minuteMax=tempMaxTime.getMinutes(); + } else { + this._defaults.minuteMax=59; + } + } + } + if (adjustSliders !== undefined && adjustSliders === true) { var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10), minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10), @@ -679,35 +749,23 @@ microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10); if (this.hour_slider) { - this.control.options(this, this.hour_slider, 'hour', {min: this._defaults.hourMin, max: hourMax}); + this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax, step: this._defaults.stepHour }); this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour)); } if (this.minute_slider) { - this.control.options(this, this.minute_slider, 'minute', { - min: this._defaults.minuteMin, - max: minMax - }); + this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax, step: this._defaults.stepMinute }); this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute)); } if (this.second_slider) { - this.control.options(this, this.second_slider, 'second', { - min: this._defaults.secondMin, - max: secMax - }); + this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax, step: this._defaults.stepSecond }); this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond)); } if (this.millisec_slider) { - this.control.options(this, this.millisec_slider, 'millisec', { - min: this._defaults.millisecMin, - max: millisecMax - }); + this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax, step: this._defaults.stepMillisec }); this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec)); } if (this.microsec_slider) { - this.control.options(this, this.microsec_slider, 'microsec', { - min: this._defaults.microsecMin, - max: microsecMax - }); + this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax, step: this._defaults.stepMicrosec }); this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec)); } } @@ -732,22 +790,22 @@ pickerTimeFormat = o.pickerTimeFormat || o.timeFormat, pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix; - if (typeof (hour) === 'object') { + if (typeof(hour) === 'object') { hour = false; } - if (typeof (minute) === 'object') { + if (typeof(minute) === 'object') { minute = false; } - if (typeof (second) === 'object') { + if (typeof(second) === 'object') { second = false; } - if (typeof (millisec) === 'object') { + if (typeof(millisec) === 'object') { millisec = false; } - if (typeof (microsec) === 'object') { + if (typeof(microsec) === 'object') { microsec = false; } - if (typeof (timezone) === 'object') { + if (typeof(timezone) === 'object') { timezone = false; } @@ -775,11 +833,11 @@ // If the update was done in the input field, the input field should not be updated. // If the update was done using the sliders, update the input field. var hasChanged = ( - hour !== parseInt(this.hour, 10) || // sliders should all be numeric - minute !== parseInt(this.minute, 10) || - second !== parseInt(this.second, 10) || - millisec !== parseInt(this.millisec, 10) || - microsec !== parseInt(this.microsec, 10) || + hour !== parseInt(this.hour,10) || // sliders should all be numeric + minute !== parseInt(this.minute,10) || + second !== parseInt(this.second,10) || + millisec !== parseInt(this.millisec,10) || + microsec !== parseInt(this.microsec,10) || (this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || (this.timezone !== null && timezone !== this.timezone.toString()) // could be numeric or "EST" format, so use toString() ); @@ -819,16 +877,22 @@ this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o); if (this.$timeObj) { if (pickerTimeFormat === o.timeFormat) { - this.$timeObj.text(this.formattedTime + pickerTimeSuffix); - } else { - this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix); + this.$timeObj.val(this.formattedTime + pickerTimeSuffix); + } + else { + this.$timeObj.val($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix); + } + if (this.$timeObj[0].setSelectionRange) { + var sPos = this.$timeObj[0].selectionStart; + var ePos = this.$timeObj[0].selectionEnd; + this.$timeObj[0].setSelectionRange(sPos, ePos); } } this.timeDefined = true; if (hasChanged) { this._updateDateTime(); - this.$input.focus(); + //this.$input.focus(); // may automatically open the picker on setDate } }, @@ -849,7 +913,7 @@ */ _updateDateTime: function (dp_inst) { dp_inst = this.inst || dp_inst; - var dtTmp = (dp_inst.currentYear > 0 ? + var dtTmp = (dp_inst.currentYear > 0? new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) : new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), dt = $.datepicker._daylightSavingAdjust(dtTmp), @@ -877,9 +941,9 @@ // return; //} - if (this._defaults.timeOnly === true) { + if (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === false) { formattedDateTime = this.formattedTime; - } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { + } else if ((this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) || (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === true)) { formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; } @@ -893,13 +957,14 @@ } else if (this.$altInput) { this.$input.val(formattedDateTime); var altFormattedDateTime = '', - altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator, - altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix; + altSeparator = this._defaults.altSeparator !== null ? this._defaults.altSeparator : this._defaults.separator, + altTimeSuffix = this._defaults.altTimeSuffix !== null ? this._defaults.altTimeSuffix : this._defaults.timeSuffix; if (!this._defaults.timeOnly) { if (this._defaults.altFormat) { altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg); - } else { + } + else { altFormattedDateTime = this.formattedDate; } @@ -908,9 +973,10 @@ } } - if (this._defaults.altTimeFormat) { + if (this._defaults.altTimeFormat !== null) { altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix; - } else { + } + else { altFormattedDateTime += this.formattedTime + altTimeSuffix; } this.$altInput.val(altFormattedDateTime); @@ -964,7 +1030,7 @@ }, options: function (tp_inst, obj, unit, opts, val) { if (tp_inst._defaults.isRTL) { - if (typeof (opts) === 'string') { + if (typeof(opts) === 'string') { if (opts === 'min' || opts === 'max') { if (val !== undefined) { return obj.slider(opts, val * -1); @@ -984,7 +1050,7 @@ } return obj.slider(opts); } - if (typeof (opts) === 'string' && val !== undefined) { + if (typeof(opts) === 'string' && val !== undefined) { return obj.slider(opts, val); } return obj.slider(opts); @@ -1005,18 +1071,16 @@ // select methods select: { create: function (tp_inst, obj, unit, val, min, max, step) { - var sel = '<select class="ui-timepicker-select" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">', + var sel = '<select class="ui-timepicker-select ui-state-default ui-corner-all" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">', format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat; for (var i = min; i <= max; i += step) { sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>'; if (unit === 'hour') { sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults); - } else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { - sel += i; - } else { - sel += '0' + i.toString(); } + else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; } + else {sel += '0' + i.toString(); } sel += '</option>'; } sel += '</select>'; @@ -1026,6 +1090,7 @@ $(sel).appendTo(obj).change(function (e) { tp_inst._onTimeChange(); tp_inst._onSelectHandler(); + tp_inst._afterInject(); }); return obj; @@ -1033,15 +1098,14 @@ options: function (tp_inst, obj, unit, opts, val) { var o = {}, $t = obj.children('select'); - if (typeof (opts) === 'string') { + if (typeof(opts) === 'string') { if (val === undefined) { return $t.data(opts); } o[opts] = val; - } else { - o = opts; } - return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step')); + else { o = opts; } + return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min>=0 ? o.min : $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step')); }, value: function (tp_inst, obj, unit, val) { var $t = obj.children('select'); @@ -1081,8 +1145,8 @@ o = o || {}; var tmp_args = arguments; - if (typeof (o) === 'string') { - if (o === 'getDate') { + if (typeof(o) === 'string') { + if (o === 'getDate' || (o === 'option' && tmp_args.length === 2 && typeof (tmp_args[1]) === 'string')) { return $.fn.datepicker.apply($(this[0]), tmp_args); } else { return this.each(function () { @@ -1180,9 +1244,7 @@ case 't': return getPatternAmpm(o.amNames, o.pmNames); default: // literal escaped in quotes - return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { - return "\\" + m; - }) + ')?'; + return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?'; } }) .replace(/\s/g, '\\s?') + @@ -1207,7 +1269,7 @@ ampm = ''; resTime.ampm = ''; } else { - ampm = $.inArray(treg[order.t].toUpperCase(), o.amNames) !== -1 ? 'AM' : 'PM'; + ampm = $.inArray(treg[order.t].toUpperCase(), $.map(o.amNames, function (x,i) { return x.toUpperCase(); })) !== -1 ? 'AM' : 'PM'; resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0]; } } @@ -1268,10 +1330,12 @@ microsec: d.getMicroseconds(), timezone: d.getTimezoneOffset() * -1 }; - } catch (err) { + } + catch (err) { try { return strictParse(f, s, o); - } catch (err2) { + } + catch (err2) { $.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f); } } @@ -1363,14 +1427,17 @@ $.datepicker._base_selectDate = $.datepicker._selectDate; $.datepicker._selectDate = function (id, dateStr) { var inst = this._getInst($(id)[0]), - tp_inst = this._get(inst, 'timepicker'); + tp_inst = this._get(inst, 'timepicker'), + was_inline; - if (tp_inst) { + if (tp_inst && inst.settings.showTimepicker) { tp_inst._limitMinMaxDateTime(inst, true); + was_inline = inst.inline; inst.inline = inst.stay_open = true; //This way the onSelect handler called from calendarpicker get the full dateTime this._base_selectDate(id, dateStr); - inst.inline = inst.stay_open = false; + inst.inline = was_inline; + inst.stay_open = false; this._notifyChange(inst); this._updateDatepicker(inst); } else { @@ -1391,7 +1458,7 @@ return; } - if (typeof (inst.stay_open) !== 'boolean' || inst.stay_open === false) { + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { this._base_updateDatepicker(inst); @@ -1459,13 +1526,15 @@ if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) { if (tp_inst._defaults.altFormat) { altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime; - } else { + } + else { altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime; } } - $(altField).val(altFormattedDateTime); + $(altField).val( inst.input.val() ? altFormattedDateTime : ""); } - } else { + } + else { $.datepicker._base_updateAlternate(inst); } }; @@ -1492,18 +1561,23 @@ }; /* - * override "Today" button to also grab the time. + * override "Today" button to also grab the time and set it to input field. */ $.datepicker._base_gotoToday = $.datepicker._gotoToday; $.datepicker._gotoToday = function (id) { - var inst = this._getInst($(id)[0]), - $dp = inst.dpDiv; + var inst = this._getInst($(id)[0]); this._base_gotoToday(id); var tp_inst = this._get(inst, 'timepicker'); - selectLocalTimezone(tp_inst); + if (!tp_inst) { + return; + } + + var tzoffset = $.timepicker.timezoneOffsetNumber(tp_inst.timezone); var now = new Date(); + now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + parseInt(tzoffset, 10)); this._setTime(inst, now); - $('.ui-datepicker-today', $dp).click(); + this._setDate(inst, now); + tp_inst._onSelectHandler(); }; /* @@ -1600,16 +1674,18 @@ * override setDate() to allow setting time too within Date object */ $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; - $.datepicker._setDateDatepicker = function (target, date) { + $.datepicker._setDateDatepicker = function (target, _date) { var inst = this._getInst(target); + var date = _date; if (!inst) { return; } - if (typeof (date) === 'string') { - date = new Date(date); + if (typeof(_date) === 'string') { + date = new Date(_date); if (!date.getTime()) { - $.timepicker.log("Error creating Date object from string."); + this._base_setDateDatepicker.apply(this, arguments); + date = $(target).datepicker('getDate'); } } @@ -1631,8 +1707,8 @@ if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { tp_inst.timezone = tp_date.getTimezoneOffset() * -1; } - date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); - tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone); + date = $.timepicker.timezoneAdjust(date, $.timepicker.timezoneOffsetString(-date.getTimezoneOffset()), tp_inst.timezone); + tp_date = $.timepicker.timezoneAdjust(tp_date, $.timepicker.timezoneOffsetString(-tp_date.getTimezoneOffset()), tp_inst.timezone); } this._updateDatepicker(inst); @@ -1659,7 +1735,27 @@ } var date = this._getDate(inst); - if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { + + var currDT = null; + + if (tp_inst.$altInput && tp_inst._defaults.altFieldTimeOnly) { + currDT = tp_inst.$input.val() + ' ' + tp_inst.$altInput.val(); + } + else if (tp_inst.$input.get(0).tagName !== 'INPUT' && tp_inst.$altInput) { + /** + * in case the datetimepicker has been applied to a non-input tag for inline UI, + * and the user has not configured the plugin to display only time in altInput, + * pick current date time from the altInput (and hope for the best, for now, until "ER1" is applied) + * + * @todo ER1. Since altInput can have a totally difference format, convert it to standard format by reading input format from "altFormat" and "altTimeFormat" option values + */ + currDT = tp_inst.$altInput.val(); + } + else { + currDT = tp_inst.$input.val(); + } + + if (date && tp_inst._parseTime(currDT, !inst.settings.timeOnly)) { date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); date.setMicroseconds(tp_inst.microsec); @@ -1671,7 +1767,7 @@ if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) { tp_inst.timezone = date.getTimezoneOffset() * -1; } - date = $.timepicker.timezoneAdjust(date, tp_inst.timezone); + date = $.timepicker.timezoneAdjust(date, tp_inst.timezone, $.timepicker.timezoneOffsetString(-date.getTimezoneOffset())); } } return date; @@ -1733,7 +1829,10 @@ onselect = null, overrides = tp_inst._defaults.evnts, fns = {}, - prop; + prop, + ret, + oldVal, + $target; if (typeof name === 'string') { // if min/max was set with the string if (name === 'minDate' || name === 'minDateTime') { min = value; @@ -1767,15 +1866,11 @@ for (prop in fns) { if (fns.hasOwnProperty(prop)) { overrides[prop] = fns[prop]; - if (!name_clone) { - name_clone = $.extend({}, name); - } + if (!name_clone) { name_clone = $.extend({}, name); } delete name_clone[prop]; } } - if (name_clone && isEmptyObject(name_clone)) { - return; - } + if (name_clone && isEmptyObject(name_clone)) { return; } if (min) { //if min was set if (min === 0) { min = new Date(); @@ -1795,6 +1890,17 @@ } else if (onselect) { tp_inst._defaults.onSelect = onselect; } + + // Datepicker will override our date when we call _base_optionDatepicker when + // calling minDate/maxDate, so we will first grab the value, call + // _base_optionDatepicker, then set our value back. + if(min || max){ + $target = $(target); + oldVal = $target.datetimepicker('getDate'); + ret = this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value); + $target.datetimepicker('setDate', oldVal); + return ret; + } } if (value === undefined) { return this._base_optionDatepicker.call($.datepicker, target, name); @@ -1979,7 +2085,7 @@ } if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back - return tzString; + return parseInt(tzString, 10); } return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus @@ -1990,13 +2096,15 @@ /** * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate) * @param {Date} date + * @param {string} fromTimezone formatted like "+0500", "-1245" * @param {string} toTimezone formatted like "+0500", "-1245" * @return {Date} */ - $.timepicker.timezoneAdjust = function (date, toTimezone) { + $.timepicker.timezoneAdjust = function (date, fromTimezone, toTimezone) { + var fromTz = $.timepicker.timezoneOffsetNumber(fromTimezone); var toTz = $.timepicker.timezoneOffsetNumber(toTimezone); if (!isNaN(toTz)) { - date.setMinutes(date.getMinutes() + -date.getTimezoneOffset() - toTz); + date.setMinutes(date.getMinutes() + (-fromTz) - (-toTz)); } return date; }; @@ -2059,6 +2167,13 @@ end: {} // options for end picker }, options); + // for the mean time this fixes an issue with calling getDate with timepicker() + var timeOnly = false; + if(method === 'timepicker'){ + timeOnly = true; + method = 'datetimepicker'; + } + function checkDates(changed, other) { var startdt = startTime[method]('getDate'), enddt = endTime[method]('getDate'), @@ -2073,9 +2188,11 @@ if (options.minInterval > 0 && minDate > enddt) { // minInterval check endTime[method]('setDate', minDate); - } else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check + } + else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check endTime[method]('setDate', maxDate); - } else if (startdt > enddt) { + } + else if (startdt > enddt) { other[method]('setDate', changeddt); } } @@ -2094,12 +2211,14 @@ date.setMilliseconds(date.getMilliseconds() - options.minInterval); } } + if (date.getTime) { other[method].call(other, 'option', option, date); } } $.fn[method].call(startTime, $.extend({ + timeOnly: timeOnly, onClose: function (dateText, inst) { checkDates($(this), endTime); }, @@ -2108,6 +2227,7 @@ } }, options, options.start)); $.fn[method].call(endTime, $.extend({ + timeOnly: timeOnly, onClose: function (dateText, inst) { checkDates($(this), startTime); }, @@ -2117,8 +2237,10 @@ }, options, options.end)); checkDates(startTime, endTime); + selected(startTime, endTime, 'minDate'); selected(endTime, startTime, 'maxDate'); + return $([startTime.get(0), endTime.get(0)]); }; @@ -2127,9 +2249,10 @@ * @param {Object} err pass any type object to log to the console during error or debugging * @return {void} */ - $.timepicker.log = function (err) { - if (window.console) { - window.console.log(err); + $.timepicker.log = function () { + // Older IE (9, maybe 10) throw error on accessing `window.console.log.apply`, so check first. + if (window.console && window.console.log && window.console.log.apply) { + window.console.log.apply(window.console, Array.prototype.slice.call(arguments)); } }; @@ -2152,9 +2275,7 @@ */ if (!Date.prototype.getMicroseconds) { Date.prototype.microseconds = 0; - Date.prototype.getMicroseconds = function () { - return this.microseconds; - }; + Date.prototype.getMicroseconds = function () { return this.microseconds; }; Date.prototype.setMicroseconds = function (m) { this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000)); this.microseconds = m % 1000; @@ -2165,6 +2286,6 @@ /* * Keep up with the version */ - $.timepicker.version = "1.4.3"; + $.timepicker.version = "1.6.3"; })); diff --git a/lib/web/jquery/ui-modules/.jshintrc b/lib/web/jquery/ui-modules/.jshintrc deleted file mode 100644 index 7bb17b5f92fef..0000000000000 --- a/lib/web/jquery/ui-modules/.jshintrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "expr": true, - "immed": true, - "noarg": true, - "quotmark": "double", - "smarttabs": true, - "trailing": true, - "undef": true, - "unused": true, - - "browser": true, - "es3": true, - "jquery": true, - - "globals": { - "define": false, - "Globalize": false - } -} diff --git a/lib/web/jquery/ui-modules/core.js b/lib/web/jquery/ui-modules/core.js index 47f7f0c0245cb..8628513918c0c 100644 --- a/lib/web/jquery/ui-modules/core.js +++ b/lib/web/jquery/ui-modules/core.js @@ -1,5 +1,7 @@ -// This file is deprecated in 1.12.0 to be removed in 1.13 +// This file is deprecated in 1.12.0 to be removed in 1.14 ( function() { +"use strict"; + define( [ "jquery", "./data", @@ -9,6 +11,7 @@ define( [ "./ie", "./keycode", "./labels", + "./jquery-patch", "./plugin", "./safe-active-element", "./safe-blur", diff --git a/lib/web/jquery/ui-modules/data.js b/lib/web/jquery/ui-modules/data.js index 1a14ed1cf2fe0..2c8fb88ddca2e 100644 --- a/lib/web/jquery/ui-modules/data.js +++ b/lib/web/jquery/ui-modules/data.js @@ -1,5 +1,5 @@ /*! - * jQuery UI :data 1.12.1 + * jQuery UI :data 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/data-selector/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,8 +24,10 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { -return $.extend( $.expr[ ":" ], { +} )( function( $ ) { +"use strict"; + +return $.extend( $.expr.pseudos, { data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) { return function( elem ) { @@ -36,4 +40,4 @@ return $.extend( $.expr[ ":" ], { return !!$.data( elem, match[ 3 ] ); } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/disable-selection.js b/lib/web/jquery/ui-modules/disable-selection.js index 6188b64687207..fd8838a3ba572 100644 --- a/lib/web/jquery/ui-modules/disable-selection.js +++ b/lib/web/jquery/ui-modules/disable-selection.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Disable Selection 1.12.1 + * jQuery UI Disable Selection 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ // This file is deprecated ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -23,7 +25,8 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; return $.fn.extend( { disableSelection: ( function() { @@ -43,4 +46,4 @@ return $.fn.extend( { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effect.js b/lib/web/jquery/ui-modules/effect.js index 88bd7874dacc2..f4733ba18f0c7 100644 --- a/lib/web/jquery/ui-modules/effect.js +++ b/lib/web/jquery/ui-modules/effect.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects 1.12.1 + * jQuery UI Effects 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9,713 +9,40 @@ //>>label: Effects Core //>>group: Effects -// jscs:disable maximumLineLength +/* eslint-disable max-len */ //>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects. -// jscs:enable maximumLineLength +/* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/category/effects-core/ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. - define( [ "jquery", "./version" ], factory ); + define( [ + "jquery", + "./jquery-var-for-color", + "./vendor/jquery-color/jquery.color", + "./version" + ], factory ); } else { // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; var dataSpace = "ui-effects-", dataSpaceStyle = "ui-effects-style", - dataSpaceAnimated = "ui-effects-animated", - - // Create a local jQuery because jQuery Color relies on it and the - // global may not exist with AMD and a custom build (#10199) - jQuery = $; + dataSpaceAnimated = "ui-effects-animated"; $.effects = { effect: {} }; -/*! - * jQuery Color Animations v2.1.2 - * https://github.com/jquery/jquery-color - * - * Copyright 2014 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * Date: Wed Jan 16 08:47:09 2013 -0600 - */ -( function( jQuery, undefined ) { - - var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + - "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", - - // Plusequals test for += 100 -= 100 - rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, - - // A set of RE's that can match strings and generate color tuples. - stringParsers = [ { - re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - parse: function( execResult ) { - return [ - execResult[ 1 ], - execResult[ 2 ], - execResult[ 3 ], - execResult[ 4 ] - ]; - } - }, { - re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - parse: function( execResult ) { - return [ - execResult[ 1 ] * 2.55, - execResult[ 2 ] * 2.55, - execResult[ 3 ] * 2.55, - execResult[ 4 ] - ]; - } - }, { - - // This regex ignores A-F because it's compared against an already lowercased string - re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, - parse: function( execResult ) { - return [ - parseInt( execResult[ 1 ], 16 ), - parseInt( execResult[ 2 ], 16 ), - parseInt( execResult[ 3 ], 16 ) - ]; - } - }, { - - // This regex ignores A-F because it's compared against an already lowercased string - re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, - parse: function( execResult ) { - return [ - parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), - parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), - parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) - ]; - } - }, { - re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, - space: "hsla", - parse: function( execResult ) { - return [ - execResult[ 1 ], - execResult[ 2 ] / 100, - execResult[ 3 ] / 100, - execResult[ 4 ] - ]; - } - } ], - - // JQuery.Color( ) - color = jQuery.Color = function( color, green, blue, alpha ) { - return new jQuery.Color.fn.parse( color, green, blue, alpha ); - }, - spaces = { - rgba: { - props: { - red: { - idx: 0, - type: "byte" - }, - green: { - idx: 1, - type: "byte" - }, - blue: { - idx: 2, - type: "byte" - } - } - }, - - hsla: { - props: { - hue: { - idx: 0, - type: "degrees" - }, - saturation: { - idx: 1, - type: "percent" - }, - lightness: { - idx: 2, - type: "percent" - } - } - } - }, - propTypes = { - "byte": { - floor: true, - max: 255 - }, - "percent": { - max: 1 - }, - "degrees": { - mod: 360, - floor: true - } - }, - support = color.support = {}, - - // Element for support tests - supportElem = jQuery( "<p>" )[ 0 ], - - // Colors = jQuery.Color.names - colors, - - // Local aliases of functions called often - each = jQuery.each; - -// Determine rgba support immediately -supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; -support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; - -// Define cache name and alpha properties -// for rgba and hsla spaces -each( spaces, function( spaceName, space ) { - space.cache = "_" + spaceName; - space.props.alpha = { - idx: 3, - type: "percent", - def: 1 - }; -} ); - -function clamp( value, prop, allowEmpty ) { - var type = propTypes[ prop.type ] || {}; - - if ( value == null ) { - return ( allowEmpty || !prop.def ) ? null : prop.def; - } - - // ~~ is an short way of doing floor for positive numbers - value = type.floor ? ~~value : parseFloat( value ); - - // IE will pass in empty strings as value for alpha, - // which will hit this case - if ( isNaN( value ) ) { - return prop.def; - } - - if ( type.mod ) { - - // We add mod before modding to make sure that negatives values - // get converted properly: -10 -> 350 - return ( value + type.mod ) % type.mod; - } - - // For now all property types without mod have min and max - return 0 > value ? 0 : type.max < value ? type.max : value; -} - -function stringParse( string ) { - var inst = color(), - rgba = inst._rgba = []; - - string = string.toLowerCase(); - - each( stringParsers, function( i, parser ) { - var parsed, - match = parser.re.exec( string ), - values = match && parser.parse( match ), - spaceName = parser.space || "rgba"; - - if ( values ) { - parsed = inst[ spaceName ]( values ); - - // If this was an rgba parse the assignment might happen twice - // oh well.... - inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; - rgba = inst._rgba = parsed._rgba; - - // Exit each( stringParsers ) here because we matched - return false; - } - } ); - - // Found a stringParser that handled it - if ( rgba.length ) { - - // If this came from a parsed string, force "transparent" when alpha is 0 - // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) - if ( rgba.join() === "0,0,0,0" ) { - jQuery.extend( rgba, colors.transparent ); - } - return inst; - } - - // Named colors - return colors[ string ]; -} - -color.fn = jQuery.extend( color.prototype, { - parse: function( red, green, blue, alpha ) { - if ( red === undefined ) { - this._rgba = [ null, null, null, null ]; - return this; - } - if ( red.jquery || red.nodeType ) { - red = jQuery( red ).css( green ); - green = undefined; - } - - var inst = this, - type = jQuery.type( red ), - rgba = this._rgba = []; - - // More than 1 argument specified - assume ( red, green, blue, alpha ) - if ( green !== undefined ) { - red = [ red, green, blue, alpha ]; - type = "array"; - } - - if ( type === "string" ) { - return this.parse( stringParse( red ) || colors._default ); - } - - if ( type === "array" ) { - each( spaces.rgba.props, function( key, prop ) { - rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); - } ); - return this; - } - - if ( type === "object" ) { - if ( red instanceof color ) { - each( spaces, function( spaceName, space ) { - if ( red[ space.cache ] ) { - inst[ space.cache ] = red[ space.cache ].slice(); - } - } ); - } else { - each( spaces, function( spaceName, space ) { - var cache = space.cache; - each( space.props, function( key, prop ) { - - // If the cache doesn't exist, and we know how to convert - if ( !inst[ cache ] && space.to ) { - - // If the value was null, we don't need to copy it - // if the key was alpha, we don't need to copy it either - if ( key === "alpha" || red[ key ] == null ) { - return; - } - inst[ cache ] = space.to( inst._rgba ); - } - - // This is the only case where we allow nulls for ALL properties. - // call clamp with alwaysAllowEmpty - inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); - } ); - - // Everything defined but alpha? - if ( inst[ cache ] && - jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { - - // Use the default of 1 - inst[ cache ][ 3 ] = 1; - if ( space.from ) { - inst._rgba = space.from( inst[ cache ] ); - } - } - } ); - } - return this; - } - }, - is: function( compare ) { - var is = color( compare ), - same = true, - inst = this; - - each( spaces, function( _, space ) { - var localCache, - isCache = is[ space.cache ]; - if ( isCache ) { - localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; - each( space.props, function( _, prop ) { - if ( isCache[ prop.idx ] != null ) { - same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); - return same; - } - } ); - } - return same; - } ); - return same; - }, - _space: function() { - var used = [], - inst = this; - each( spaces, function( spaceName, space ) { - if ( inst[ space.cache ] ) { - used.push( spaceName ); - } - } ); - return used.pop(); - }, - transition: function( other, distance ) { - var end = color( other ), - spaceName = end._space(), - space = spaces[ spaceName ], - startColor = this.alpha() === 0 ? color( "transparent" ) : this, - start = startColor[ space.cache ] || space.to( startColor._rgba ), - result = start.slice(); - - end = end[ space.cache ]; - each( space.props, function( key, prop ) { - var index = prop.idx, - startValue = start[ index ], - endValue = end[ index ], - type = propTypes[ prop.type ] || {}; - - // If null, don't override start value - if ( endValue === null ) { - return; - } - - // If null - use end - if ( startValue === null ) { - result[ index ] = endValue; - } else { - if ( type.mod ) { - if ( endValue - startValue > type.mod / 2 ) { - startValue += type.mod; - } else if ( startValue - endValue > type.mod / 2 ) { - startValue -= type.mod; - } - } - result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); - } - } ); - return this[ spaceName ]( result ); - }, - blend: function( opaque ) { - - // If we are already opaque - return ourself - if ( this._rgba[ 3 ] === 1 ) { - return this; - } - - var rgb = this._rgba.slice(), - a = rgb.pop(), - blend = color( opaque )._rgba; - - return color( jQuery.map( rgb, function( v, i ) { - return ( 1 - a ) * blend[ i ] + a * v; - } ) ); - }, - toRgbaString: function() { - var prefix = "rgba(", - rgba = jQuery.map( this._rgba, function( v, i ) { - return v == null ? ( i > 2 ? 1 : 0 ) : v; - } ); - - if ( rgba[ 3 ] === 1 ) { - rgba.pop(); - prefix = "rgb("; - } - - return prefix + rgba.join() + ")"; - }, - toHslaString: function() { - var prefix = "hsla(", - hsla = jQuery.map( this.hsla(), function( v, i ) { - if ( v == null ) { - v = i > 2 ? 1 : 0; - } - - // Catch 1 and 2 - if ( i && i < 3 ) { - v = Math.round( v * 100 ) + "%"; - } - return v; - } ); - - if ( hsla[ 3 ] === 1 ) { - hsla.pop(); - prefix = "hsl("; - } - return prefix + hsla.join() + ")"; - }, - toHexString: function( includeAlpha ) { - var rgba = this._rgba.slice(), - alpha = rgba.pop(); - - if ( includeAlpha ) { - rgba.push( ~~( alpha * 255 ) ); - } - - return "#" + jQuery.map( rgba, function( v ) { - - // Default to 0 when nulls exist - v = ( v || 0 ).toString( 16 ); - return v.length === 1 ? "0" + v : v; - } ).join( "" ); - }, - toString: function() { - return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); - } -} ); -color.fn.parse.prototype = color.fn; - -// Hsla conversions adapted from: -// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 - -function hue2rgb( p, q, h ) { - h = ( h + 1 ) % 1; - if ( h * 6 < 1 ) { - return p + ( q - p ) * h * 6; - } - if ( h * 2 < 1 ) { - return q; - } - if ( h * 3 < 2 ) { - return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; - } - return p; -} - -spaces.hsla.to = function( rgba ) { - if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { - return [ null, null, null, rgba[ 3 ] ]; - } - var r = rgba[ 0 ] / 255, - g = rgba[ 1 ] / 255, - b = rgba[ 2 ] / 255, - a = rgba[ 3 ], - max = Math.max( r, g, b ), - min = Math.min( r, g, b ), - diff = max - min, - add = max + min, - l = add * 0.5, - h, s; - - if ( min === max ) { - h = 0; - } else if ( r === max ) { - h = ( 60 * ( g - b ) / diff ) + 360; - } else if ( g === max ) { - h = ( 60 * ( b - r ) / diff ) + 120; - } else { - h = ( 60 * ( r - g ) / diff ) + 240; - } - - // Chroma (diff) == 0 means greyscale which, by definition, saturation = 0% - // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) - if ( diff === 0 ) { - s = 0; - } else if ( l <= 0.5 ) { - s = diff / add; - } else { - s = diff / ( 2 - add ); - } - return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; -}; - -spaces.hsla.from = function( hsla ) { - if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { - return [ null, null, null, hsla[ 3 ] ]; - } - var h = hsla[ 0 ] / 360, - s = hsla[ 1 ], - l = hsla[ 2 ], - a = hsla[ 3 ], - q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, - p = 2 * l - q; - - return [ - Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), - Math.round( hue2rgb( p, q, h ) * 255 ), - Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), - a - ]; -}; - -each( spaces, function( spaceName, space ) { - var props = space.props, - cache = space.cache, - to = space.to, - from = space.from; - - // Makes rgba() and hsla() - color.fn[ spaceName ] = function( value ) { - - // Generate a cache for this space if it doesn't exist - if ( to && !this[ cache ] ) { - this[ cache ] = to( this._rgba ); - } - if ( value === undefined ) { - return this[ cache ].slice(); - } - - var ret, - type = jQuery.type( value ), - arr = ( type === "array" || type === "object" ) ? value : arguments, - local = this[ cache ].slice(); - - each( props, function( key, prop ) { - var val = arr[ type === "object" ? key : prop.idx ]; - if ( val == null ) { - val = local[ prop.idx ]; - } - local[ prop.idx ] = clamp( val, prop ); - } ); - - if ( from ) { - ret = color( from( local ) ); - ret[ cache ] = local; - return ret; - } else { - return color( local ); - } - }; - - // Makes red() green() blue() alpha() hue() saturation() lightness() - each( props, function( key, prop ) { - - // Alpha is included in more than one space - if ( color.fn[ key ] ) { - return; - } - color.fn[ key ] = function( value ) { - var vtype = jQuery.type( value ), - fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), - local = this[ fn ](), - cur = local[ prop.idx ], - match; - - if ( vtype === "undefined" ) { - return cur; - } - - if ( vtype === "function" ) { - value = value.call( this, cur ); - vtype = jQuery.type( value ); - } - if ( value == null && prop.empty ) { - return this; - } - if ( vtype === "string" ) { - match = rplusequals.exec( value ); - if ( match ) { - value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); - } - } - local[ prop.idx ] = value; - return this[ fn ]( local ); - }; - } ); -} ); - -// Add cssHook and .fx.step function for each named hook. -// accept a space separated string of properties -color.hook = function( hook ) { - var hooks = hook.split( " " ); - each( hooks, function( i, hook ) { - jQuery.cssHooks[ hook ] = { - set: function( elem, value ) { - var parsed, curElem, - backgroundColor = ""; - - if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || - ( parsed = stringParse( value ) ) ) ) { - value = color( parsed || value ); - if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { - curElem = hook === "backgroundColor" ? elem.parentNode : elem; - while ( - ( backgroundColor === "" || backgroundColor === "transparent" ) && - curElem && curElem.style - ) { - try { - backgroundColor = jQuery.css( curElem, "backgroundColor" ); - curElem = curElem.parentNode; - } catch ( e ) { - } - } - - value = value.blend( backgroundColor && backgroundColor !== "transparent" ? - backgroundColor : - "_default" ); - } - - value = value.toRgbaString(); - } - try { - elem.style[ hook ] = value; - } catch ( e ) { - - // Wrapped to prevent IE from throwing errors on "invalid" values like - // 'auto' or 'inherit' - } - } - }; - jQuery.fx.step[ hook ] = function( fx ) { - if ( !fx.colorInit ) { - fx.start = color( fx.elem, hook ); - fx.end = color( fx.end ); - fx.colorInit = true; - } - jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); - }; - } ); - -}; - -color.hook( stepHooks ); - -jQuery.cssHooks.borderColor = { - expand: function( value ) { - var expanded = {}; - - each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { - expanded[ "border" + part + "Color" ] = value; - } ); - return expanded; - } -}; - -// Basic color names only. -// Usage of any of the other color names requires adding yourself or including -// jquery.color.svg-names.js. -colors = jQuery.Color.names = { - - // 4.1. Basic color keywords - aqua: "#00ffff", - black: "#000000", - blue: "#0000ff", - fuchsia: "#ff00ff", - gray: "#808080", - green: "#008000", - lime: "#00ff00", - maroon: "#800000", - navy: "#000080", - olive: "#808000", - purple: "#800080", - red: "#ff0000", - silver: "#c0c0c0", - teal: "#008080", - white: "#ffffff", - yellow: "#ffff00", - - // 4.2.3. "transparent" color keyword - transparent: [ null, null, null, 0 ], - - _default: "#ffffff" -}; - -} )( jQuery ); - /******************************************************************************/ /****************************** CLASS ANIMATIONS ******************************/ /******************************************************************************/ @@ -746,6 +73,12 @@ $.each( } ); +function camelCase( string ) { + return string.replace( /-([\da-z])/gi, function( all, letter ) { + return letter.toUpperCase(); + } ); +} + function getElementStyles( elem ) { var key, len, style = elem.ownerDocument.defaultView ? @@ -758,7 +91,7 @@ function getElementStyles( elem ) { while ( len-- ) { key = style[ len ]; if ( typeof style[ key ] === "string" ) { - styles[ $.camelCase( key ) ] = style[ key ]; + styles[ camelCase( key ) ] = style[ key ]; } } @@ -932,12 +265,12 @@ $.fn.extend( { ( function() { -if ( $.expr && $.expr.filters && $.expr.filters.animated ) { - $.expr.filters.animated = ( function( orig ) { +if ( $.expr && $.expr.pseudos && $.expr.pseudos.animated ) { + $.expr.pseudos.animated = ( function( orig ) { return function( elem ) { return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); }; - } )( $.expr.filters.animated ); + } )( $.expr.pseudos.animated ); } if ( $.uiBackCompat !== false ) { @@ -1006,6 +339,7 @@ if ( $.uiBackCompat !== false ) { // Firefox incorrectly exposes anonymous content // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 try { + // eslint-disable-next-line no-unused-expressions active.id; } catch ( e ) { active = document.body; @@ -1068,7 +402,7 @@ if ( $.uiBackCompat !== false ) { } $.extend( $.effects, { - version: "1.12.1", + version: "1.13.0", define: function( name, mode, effect ) { if ( !effect ) { @@ -1284,7 +618,7 @@ function _normalizeArguments( effect, options, speed, callback ) { } // Catch (effect, callback) - if ( $.isFunction( options ) ) { + if ( typeof options === "function" ) { callback = options; speed = null; options = {}; @@ -1298,7 +632,7 @@ function _normalizeArguments( effect, options, speed, callback ) { } // Catch (effect, options, callback) - if ( $.isFunction( speed ) ) { + if ( typeof speed === "function" ) { callback = speed; speed = null; } @@ -1332,7 +666,7 @@ function standardAnimationOption( option ) { } // Complete callback - if ( $.isFunction( option ) ) { + if ( typeof option === "function" ) { return true; } @@ -1359,7 +693,7 @@ $.fn.extend( { var el = $( this ), normalizedMode = $.effects.mode( el, mode ) || defaultMode; - // Sentinel for duck-punching the :animated psuedo-selector + // Sentinel for duck-punching the :animated pseudo-selector el.data( dataSpaceAnimated, true ); // Save effect mode for later use, @@ -1367,7 +701,7 @@ $.fn.extend( { // as the .show() below destroys the initial state modes.push( normalizedMode ); - // See $.uiBackCompat inside of run() for removal of defaultMode in 1.13 + // See $.uiBackCompat inside of run() for removal of defaultMode in 1.14 if ( defaultMode && ( normalizedMode === "show" || ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { el.show(); @@ -1377,7 +711,7 @@ $.fn.extend( { $.effects.saveStyle( el ); } - if ( $.isFunction( next ) ) { + if ( typeof next === "function" ) { next(); } }; @@ -1412,11 +746,11 @@ $.fn.extend( { } function done() { - if ( $.isFunction( complete ) ) { + if ( typeof complete === "function" ) { complete.call( elem[ 0 ] ); } - if ( $.isFunction( next ) ) { + if ( typeof next === "function" ) { next(); } } @@ -1525,22 +859,24 @@ $.fn.extend( { width: target.innerWidth() }, startPosition = element.offset(), - transfer = $( "<div class='ui-effects-transfer'></div>" ) - .appendTo( "body" ) - .addClass( options.className ) - .css( { - top: startPosition.top - fixTop, - left: startPosition.left - fixLeft, - height: element.innerHeight(), - width: element.innerWidth(), - position: targetFixed ? "fixed" : "absolute" - } ) - .animate( animation, options.duration, options.easing, function() { - transfer.remove(); - if ( $.isFunction( done ) ) { - done(); - } - } ); + transfer = $( "<div class='ui-effects-transfer'></div>" ); + + transfer + .appendTo( "body" ) + .addClass( options.className ) + .css( { + top: startPosition.top - fixTop, + left: startPosition.left - fixLeft, + height: element.innerHeight(), + width: element.innerWidth(), + position: targetFixed ? "fixed" : "absolute" + } ) + .animate( animation, options.duration, options.easing, function() { + transfer.remove(); + if ( typeof done === "function" ) { + done(); + } + } ); } } ); @@ -1632,4 +968,4 @@ $.each( baseEasings, function( name, easeIn ) { return $.effects; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-blind.js b/lib/web/jquery/ui-modules/effects/effect-blind.js index 1ec267441a6fd..3b65a4f62874c 100644 --- a/lib/web/jquery/ui-modules/effects/effect-blind.js +++ b/lib/web/jquery/ui-modules/effects/effect-blind.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Blind 1.12.1 + * jQuery UI Effects Blind 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "blind", "hide", function( options, done ) { var map = { @@ -67,4 +70,4 @@ return $.effects.define( "blind", "hide", function( options, done ) { } ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-bounce.js b/lib/web/jquery/ui-modules/effects/effect-bounce.js index 1a6304906ebcc..76e0f8501cf31 100644 --- a/lib/web/jquery/ui-modules/effects/effect-bounce.js +++ b/lib/web/jquery/ui-modules/effects/effect-bounce.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Bounce 1.12.1 + * jQuery UI Effects Bounce 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "bounce", function( options, done ) { var upAnim, downAnim, refValue, @@ -107,4 +110,4 @@ return $.effects.define( "bounce", function( options, done ) { $.effects.unshift( element, queuelen, anims + 1 ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-clip.js b/lib/web/jquery/ui-modules/effects/effect-clip.js index 5609fc8617714..12b49f486aaaa 100644 --- a/lib/web/jquery/ui-modules/effects/effect-clip.js +++ b/lib/web/jquery/ui-modules/effects/effect-clip.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Clip 1.12.1 + * jQuery UI Effects Clip 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "clip", "hide", function( options, done ) { var start, @@ -62,4 +65,4 @@ return $.effects.define( "clip", "hide", function( options, done ) { } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-drop.js b/lib/web/jquery/ui-modules/effects/effect-drop.js index 428d49bd8c696..8f3ba74c662ef 100644 --- a/lib/web/jquery/ui-modules/effects/effect-drop.js +++ b/lib/web/jquery/ui-modules/effects/effect-drop.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Drop 1.12.1 + * jQuery UI Effects Drop 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "drop", "hide", function( options, done ) { @@ -66,4 +69,4 @@ return $.effects.define( "drop", "hide", function( options, done ) { } ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-explode.js b/lib/web/jquery/ui-modules/effects/effect-explode.js index 18f892fc42c17..f3976fcfea6e0 100644 --- a/lib/web/jquery/ui-modules/effects/effect-explode.js +++ b/lib/web/jquery/ui-modules/effects/effect-explode.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Explode 1.12.1 + * jQuery UI Effects Explode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9,13 +9,15 @@ //>>label: Explode Effect //>>group: Effects -// jscs:disable maximumLineLength +/* eslint-disable max-len */ //>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness. -// jscs:enable maximumLineLength +/* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/explode-effect/ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -29,7 +31,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "explode", "hide", function( options, done ) { @@ -108,4 +111,4 @@ return $.effects.define( "explode", "hide", function( options, done ) { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-fade.js b/lib/web/jquery/ui-modules/effects/effect-fade.js index 9bc29f7a644cd..f84d6c40f3cae 100644 --- a/lib/web/jquery/ui-modules/effects/effect-fade.js +++ b/lib/web/jquery/ui-modules/effects/effect-fade.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Fade 1.12.1 + * jQuery UI Effects Fade 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "fade", "toggle", function( options, done ) { var show = options.mode === "show"; @@ -44,4 +47,4 @@ return $.effects.define( "fade", "toggle", function( options, done ) { } ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-fold.js b/lib/web/jquery/ui-modules/effects/effect-fold.js index ccfd67a558d0d..682af572ee8df 100644 --- a/lib/web/jquery/ui-modules/effects/effect-fold.js +++ b/lib/web/jquery/ui-modules/effects/effect-fold.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Fold 1.12.1 + * jQuery UI Effects Fold 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "fold", "hide", function( options, done ) { @@ -86,4 +89,4 @@ return $.effects.define( "fold", "hide", function( options, done ) { $.effects.unshift( element, queuelen, 4 ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-highlight.js b/lib/web/jquery/ui-modules/effects/effect-highlight.js index 546e4714666b8..41ecc6f7c4227 100644 --- a/lib/web/jquery/ui-modules/effects/effect-highlight.js +++ b/lib/web/jquery/ui-modules/effects/effect-highlight.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Highlight 1.12.1 + * jQuery UI Effects Highlight 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "highlight", "show", function( options, done ) { var element = $( this ), @@ -54,4 +57,4 @@ return $.effects.define( "highlight", "show", function( options, done ) { } ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-puff.js b/lib/web/jquery/ui-modules/effects/effect-puff.js index f5e0910cfda12..444884376300f 100644 --- a/lib/web/jquery/ui-modules/effects/effect-puff.js +++ b/lib/web/jquery/ui-modules/effects/effect-puff.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Puff 1.12.1 + * jQuery UI Effects Puff 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -28,7 +30,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "puff", "hide", function( options, done ) { var newOptions = $.extend( true, {}, options, { @@ -39,4 +42,4 @@ return $.effects.define( "puff", "hide", function( options, done ) { $.effects.effect.scale.call( this, newOptions, done ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-pulsate.js b/lib/web/jquery/ui-modules/effects/effect-pulsate.js index 8d6826f9f0fde..0787e2aa1df7d 100644 --- a/lib/web/jquery/ui-modules/effects/effect-pulsate.js +++ b/lib/web/jquery/ui-modules/effects/effect-pulsate.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Pulsate 1.12.1 + * jQuery UI Effects Pulsate 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "pulsate", "show", function( options, done ) { var element = $( this ), @@ -61,4 +64,4 @@ return $.effects.define( "pulsate", "show", function( options, done ) { $.effects.unshift( element, queuelen, anims + 1 ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-scale.js b/lib/web/jquery/ui-modules/effects/effect-scale.js index 477d8c8ba1450..0e4c291e2ec56 100644 --- a/lib/web/jquery/ui-modules/effects/effect-scale.js +++ b/lib/web/jquery/ui-modules/effects/effect-scale.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Scale 1.12.1 + * jQuery UI Effects Scale 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -28,7 +30,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "scale", function( options, done ) { @@ -53,4 +56,4 @@ return $.effects.define( "scale", function( options, done ) { $.effects.effect.size.call( this, newOptions, done ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-shake.js b/lib/web/jquery/ui-modules/effects/effect-shake.js index 37c0089baed8e..b12eb1f1264e7 100644 --- a/lib/web/jquery/ui-modules/effects/effect-shake.js +++ b/lib/web/jquery/ui-modules/effects/effect-shake.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Shake 1.12.1 + * jQuery UI Effects Shake 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "shake", function( options, done ) { @@ -71,4 +74,4 @@ return $.effects.define( "shake", function( options, done ) { $.effects.unshift( element, queuelen, anims + 1 ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-size.js b/lib/web/jquery/ui-modules/effects/effect-size.js index 5cb26016d8806..91aeea7faed05 100644 --- a/lib/web/jquery/ui-modules/effects/effect-size.js +++ b/lib/web/jquery/ui-modules/effects/effect-size.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Size 1.12.1 + * jQuery UI Effects Size 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "size", function( options, done ) { @@ -105,6 +108,8 @@ return $.effects.define( "size", function( options, done ) { to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; } + delete from.outerHeight; + delete from.outerWidth; element.css( from ); // Animate the children if desired @@ -188,4 +193,4 @@ return $.effects.define( "size", function( options, done ) { } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-slide.js b/lib/web/jquery/ui-modules/effects/effect-slide.js index 84f2893182156..1ce84f03e0a33 100644 --- a/lib/web/jquery/ui-modules/effects/effect-slide.js +++ b/lib/web/jquery/ui-modules/effects/effect-slide.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Slide 1.12.1 + * jQuery UI Effects Slide 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.effects.define( "slide", "show", function( options, done ) { var startClip, startRef, @@ -73,4 +76,4 @@ return $.effects.define( "slide", "show", function( options, done ) { } ); } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/effects/effect-transfer.js b/lib/web/jquery/ui-modules/effects/effect-transfer.js index dffcdab420364..231a4e6f5c8ec 100644 --- a/lib/web/jquery/ui-modules/effects/effect-transfer.js +++ b/lib/web/jquery/ui-modules/effects/effect-transfer.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Effects Transfer 1.12.1 + * jQuery UI Effects Transfer 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/effect/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; var effect; if ( $.uiBackCompat !== false ) { @@ -37,4 +40,4 @@ if ( $.uiBackCompat !== false ) { } return effect; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/escape-selector.js b/lib/web/jquery/ui-modules/escape-selector.js deleted file mode 100644 index d43e4f816686d..0000000000000 --- a/lib/web/jquery/ui-modules/escape-selector.js +++ /dev/null @@ -1,21 +0,0 @@ -( function( factory ) { - if ( typeof define === "function" && define.amd ) { - - // AMD. Register as an anonymous module. - define( [ "jquery", "./version" ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -} ( function( $ ) { - -// Internal use only -return $.ui.escapeSelector = ( function() { - var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g; - return function( selector ) { - return selector.replace( selectorEscape, "\\$1" ); - }; -} )(); - -} ) ); diff --git a/lib/web/jquery/ui-modules/focusable.js b/lib/web/jquery/ui-modules/focusable.js index 44992890d336a..3e07f44df2385 100644 --- a/lib/web/jquery/ui-modules/focusable.js +++ b/lib/web/jquery/ui-modules/focusable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Focusable 1.12.1 + * jQuery UI Focusable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/focusable-selector/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,7 +24,8 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; // Selectors $.ui.focusable = function( element, hasTabindex ) { @@ -70,10 +73,10 @@ function visible( element ) { element = element.parent(); visibility = element.css( "visibility" ); } - return visibility !== "hidden"; + return visibility === "visible"; } -$.extend( $.expr[ ":" ], { +$.extend( $.expr.pseudos, { focusable: function( element ) { return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); } @@ -81,4 +84,4 @@ $.extend( $.expr[ ":" ], { return $.ui.focusable; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/form-reset-mixin.js b/lib/web/jquery/ui-modules/form-reset-mixin.js index 74be1d5b9a783..9b463a4846243 100644 --- a/lib/web/jquery/ui-modules/form-reset-mixin.js +++ b/lib/web/jquery/ui-modules/form-reset-mixin.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Form Reset Mixin 1.12.1 + * jQuery UI Form Reset Mixin 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/form-reset-mixin/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -26,7 +28,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.ui.formResetMixin = { _formResetHandler: function() { @@ -42,7 +45,7 @@ return $.ui.formResetMixin = { }, _bindFormResetHandler: function() { - this.form = this.element.form(); + this.form = this.element._form(); if ( !this.form.length ) { return; } @@ -74,4 +77,4 @@ return $.ui.formResetMixin = { } }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/form.js b/lib/web/jquery/ui-modules/form.js index cfb4bd280df04..60b052277c9f5 100644 --- a/lib/web/jquery/ui-modules/form.js +++ b/lib/web/jquery/ui-modules/form.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,13 +10,14 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; // Support: IE8 Only // IE8 does not support the form attribute and when it is supplied. It overwrites the form prop // with a string, so we need to find the proper form. -return $.fn.form = function() { +return $.fn._form = function() { return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-af.js b/lib/web/jquery/ui-modules/i18n/datepicker-af.js index c756888846b0a..d239561379641 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-af.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-af.js @@ -1,6 +1,8 @@ /* Afrikaans initialisation for the jQuery UI date picker plugin. */ /* Written by Renier Pretorius. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.af = { closeText: "Selekteer", prevText: "Vorige", nextText: "Volgende", currentText: "Vandag", - monthNames: [ "Januarie","Februarie","Maart","April","Mei","Junie", - "Julie","Augustus","September","Oktober","November","Desember" ], + monthNames: [ "Januarie", "Februarie", "Maart", "April", "Mei", "Junie", + "Julie", "Augustus", "September", "Oktober", "November", "Desember" ], monthNamesShort: [ "Jan", "Feb", "Mrt", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des" ], dayNames: [ "Sondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrydag", "Saterdag" ], dayNamesShort: [ "Son", "Maa", "Din", "Woe", "Don", "Vry", "Sat" ], - dayNamesMin: [ "So","Ma","Di","Wo","Do","Vr","Sa" ], + dayNamesMin: [ "So", "Ma", "Di", "Wo", "Do", "Vr", "Sa" ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.af ); return datepicker.regional.af; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ar-DZ.js b/lib/web/jquery/ui-modules/i18n/datepicker-ar-DZ.js index a2b1750b665b1..8dc805986f294 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ar-DZ.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ar-DZ.js @@ -4,6 +4,8 @@ /* Mohamed Amine HADDAD -- zatamine@gmail.com */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -13,7 +15,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "ar-DZ" ] = { closeText: "إغلاق", @@ -21,7 +24,7 @@ datepicker.regional[ "ar-DZ" ] = { nextText: "التالي>", currentText: "اليوم", monthNames: [ "جانفي", "فيفري", "مارس", "أفريل", "ماي", "جوان", - "جويلية", "أوت", "سبتمبر","أكتوبر", "نوفمبر", "ديسمبر" ], + "جويلية", "أوت", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر" ], monthNamesShort: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" ], dayNames: [ "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت" ], dayNamesShort: [ "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت" ], @@ -36,4 +39,4 @@ datepicker.setDefaults( datepicker.regional[ "ar-DZ" ] ); return datepicker.regional[ "ar-DZ" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ar.js b/lib/web/jquery/ui-modules/i18n/datepicker-ar.js index 95784e88cf6e3..31d7ee565ed34 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ar.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ar.js @@ -4,6 +4,8 @@ /* Written by Mohammed Alshehri -- m@dralshehri.com */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -13,7 +15,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ar = { closeText: "إغلاق", @@ -36,4 +39,4 @@ datepicker.setDefaults( datepicker.regional.ar ); return datepicker.regional.ar; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-az.js b/lib/web/jquery/ui-modules/i18n/datepicker-az.js index 2ebdcfa8b5723..d02d3561f6f99 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-az.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-az.js @@ -1,6 +1,8 @@ /* Azerbaijani (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Jamil Najafov (necefov33@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.az = { closeText: "Bağla", prevText: "<Geri", nextText: "İrəli>", currentText: "Bugün", - monthNames: [ "Yanvar","Fevral","Mart","Aprel","May","İyun", - "İyul","Avqust","Sentyabr","Oktyabr","Noyabr","Dekabr" ], - monthNamesShort: [ "Yan","Fev","Mar","Apr","May","İyun", - "İyul","Avq","Sen","Okt","Noy","Dek" ], - dayNames: [ "Bazar","Bazar ertəsi","Çərşənbə axşamı","Çərşənbə","Cümə axşamı","Cümə","Şənbə" ], - dayNamesShort: [ "B","Be","Ça","Ç","Ca","C","Ş" ], - dayNamesMin: [ "B","B","Ç","С","Ç","C","Ş" ], + monthNames: [ "Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun", + "İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr" ], + monthNamesShort: [ "Yan", "Fev", "Mar", "Apr", "May", "İyun", + "İyul", "Avq", "Sen", "Okt", "Noy", "Dek" ], + dayNames: [ "Bazar", "Bazar ertəsi", "Çərşənbə axşamı", "Çərşənbə", "Cümə axşamı", "Cümə", "Şənbə" ], + dayNamesShort: [ "B", "Be", "Ça", "Ç", "Ca", "C", "Ş" ], + dayNamesMin: [ "B", "B", "Ç", "С", "Ç", "C", "Ş" ], weekHeader: "Hf", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.az ); return datepicker.regional.az; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-be.js b/lib/web/jquery/ui-modules/i18n/datepicker-be.js index 7d96dd1da659d..51ddd6e572241 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-be.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-be.js @@ -1,6 +1,8 @@ /* Belarusian initialisation for the jQuery UI date picker plugin. */ /* Written by Pavel Selitskas <p.selitskas@gmail.com> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.be = { closeText: "Зачыніць", prevText: "←Папяр.", nextText: "Наст.→", currentText: "Сёньня", - monthNames: [ "Студзень","Люты","Сакавік","Красавік","Травень","Чэрвень", - "Ліпень","Жнівень","Верасень","Кастрычнік","Лістапад","Сьнежань" ], - monthNamesShort: [ "Сту","Лют","Сак","Кра","Тра","Чэр", - "Ліп","Жні","Вер","Кас","Ліс","Сьн" ], - dayNames: [ "нядзеля","панядзелак","аўторак","серада","чацьвер","пятніца","субота" ], - dayNamesShort: [ "ндз","пнд","аўт","срд","чцв","птн","сбт" ], - dayNamesMin: [ "Нд","Пн","Аў","Ср","Чц","Пт","Сб" ], + monthNames: [ "Студзень", "Люты", "Сакавік", "Красавік", "Травень", "Чэрвень", + "Ліпень", "Жнівень", "Верасень", "Кастрычнік", "Лістапад", "Сьнежань" ], + monthNamesShort: [ "Сту", "Лют", "Сак", "Кра", "Тра", "Чэр", + "Ліп", "Жні", "Вер", "Кас", "Ліс", "Сьн" ], + dayNames: [ "нядзеля", "панядзелак", "аўторак", "серада", "чацьвер", "пятніца", "субота" ], + dayNamesShort: [ "ндз", "пнд", "аўт", "срд", "чцв", "птн", "сбт" ], + dayNamesMin: [ "Нд", "Пн", "Аў", "Ср", "Чц", "Пт", "Сб" ], weekHeader: "Тд", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.be ); return datepicker.regional.be; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-bg.js b/lib/web/jquery/ui-modules/i18n/datepicker-bg.js index cb066a4c9df9a..0344de4fb0b39 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-bg.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-bg.js @@ -1,6 +1,8 @@ /* Bulgarian initialisation for the jQuery UI date picker plugin. */ /* Written by Stoyan Kyosev (http://svest.org). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.bg = { closeText: "затвори", @@ -18,13 +21,13 @@ datepicker.regional.bg = { nextText: "напред>", nextBigText: ">>", currentText: "днес", - monthNames: [ "Януари","Февруари","Март","Април","Май","Юни", - "Юли","Август","Септември","Октомври","Ноември","Декември" ], - monthNamesShort: [ "Яну","Фев","Мар","Апр","Май","Юни", - "Юли","Авг","Сеп","Окт","Нов","Дек" ], - dayNames: [ "Неделя","Понеделник","Вторник","Сряда","Четвъртък","Петък","Събота" ], - dayNamesShort: [ "Нед","Пон","Вто","Сря","Чет","Пет","Съб" ], - dayNamesMin: [ "Не","По","Вт","Ср","Че","Пе","Съ" ], + monthNames: [ "Януари", "Февруари", "Март", "Април", "Май", "Юни", + "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември" ], + monthNamesShort: [ "Яну", "Фев", "Мар", "Апр", "Май", "Юни", + "Юли", "Авг", "Сеп", "Окт", "Нов", "Дек" ], + dayNames: [ "Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота" ], + dayNamesShort: [ "Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб" ], + dayNamesMin: [ "Не", "По", "Вт", "Ср", "Че", "Пе", "Съ" ], weekHeader: "Wk", dateFormat: "dd.mm.yy", firstDay: 1, @@ -35,4 +38,4 @@ datepicker.setDefaults( datepicker.regional.bg ); return datepicker.regional.bg; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-bs.js b/lib/web/jquery/ui-modules/i18n/datepicker-bs.js index b9f2e286917ec..a5c145affc084 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-bs.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-bs.js @@ -1,6 +1,8 @@ /* Bosnian i18n for the jQuery UI date picker plugin. */ /* Written by Kenan Konjo. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.bs = { closeText: "Zatvori", prevText: "<", nextText: ">", currentText: "Danas", - monthNames: [ "Januar","Februar","Mart","April","Maj","Juni", - "Juli","August","Septembar","Oktobar","Novembar","Decembar" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Aug","Sep","Okt","Nov","Dec" ], - dayNames: [ "Nedelja","Ponedeljak","Utorak","Srijeda","Četvrtak","Petak","Subota" ], - dayNamesShort: [ "Ned","Pon","Uto","Sri","Čet","Pet","Sub" ], - dayNamesMin: [ "Ne","Po","Ut","Sr","Če","Pe","Su" ], + monthNames: [ "Januar", "Februar", "Mart", "April", "Maj", "Juni", + "Juli", "August", "Septembar", "Oktobar", "Novembar", "Decembar" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "Nedelja", "Ponedeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota" ], + dayNamesShort: [ "Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub" ], + dayNamesMin: [ "Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su" ], weekHeader: "Wk", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.bs ); return datepicker.regional.bs; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ca.js b/lib/web/jquery/ui-modules/i18n/datepicker-ca.js index 9febd90eef623..bca86decfd670 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ca.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ca.js @@ -1,6 +1,8 @@ /* Inicialització en català per a l'extensió 'UI date picker' per jQuery. */ /* Writers: (joan.leon@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ca = { closeText: "Tanca", prevText: "Anterior", nextText: "Següent", currentText: "Avui", - monthNames: [ "gener","febrer","març","abril","maig","juny", - "juliol","agost","setembre","octubre","novembre","desembre" ], - monthNamesShort: [ "gen","feb","març","abr","maig","juny", - "jul","ag","set","oct","nov","des" ], - dayNames: [ "diumenge","dilluns","dimarts","dimecres","dijous","divendres","dissabte" ], - dayNamesShort: [ "dg","dl","dt","dc","dj","dv","ds" ], - dayNamesMin: [ "dg","dl","dt","dc","dj","dv","ds" ], + monthNames: [ "gener", "febrer", "març", "abril", "maig", "juny", + "juliol", "agost", "setembre", "octubre", "novembre", "desembre" ], + monthNamesShort: [ "gen", "feb", "març", "abr", "maig", "juny", + "jul", "ag", "set", "oct", "nov", "des" ], + dayNames: [ "diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte" ], + dayNamesShort: [ "dg", "dl", "dt", "dc", "dj", "dv", "ds" ], + dayNamesMin: [ "dg", "dl", "dt", "dc", "dj", "dv", "ds" ], weekHeader: "Set", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ca ); return datepicker.regional.ca; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-cs.js b/lib/web/jquery/ui-modules/i18n/datepicker-cs.js index c2f79cf9e41d9..201fac7b81fac 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-cs.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-cs.js @@ -1,6 +1,8 @@ /* Czech initialisation for the jQuery UI date picker plugin. */ /* Written by Tomas Muller (tomas@tomas-muller.net). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.cs = { closeText: "Zavřít", prevText: "<Dříve", nextText: "Později>", currentText: "Nyní", - monthNames: [ "leden","únor","březen","duben","květen","červen", - "červenec","srpen","září","říjen","listopad","prosinec" ], - monthNamesShort: [ "led","úno","bře","dub","kvě","čer", - "čvc","srp","zář","říj","lis","pro" ], + monthNames: [ "leden", "únor", "březen", "duben", "květen", "červen", + "červenec", "srpen", "září", "říjen", "listopad", "prosinec" ], + monthNamesShort: [ "led", "úno", "bře", "dub", "kvě", "čer", + "čvc", "srp", "zář", "říj", "lis", "pro" ], dayNames: [ "neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota" ], dayNamesShort: [ "ne", "po", "út", "st", "čt", "pá", "so" ], - dayNamesMin: [ "ne","po","út","st","čt","pá","so" ], + dayNamesMin: [ "ne", "po", "út", "st", "čt", "pá", "so" ], weekHeader: "Týd", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.cs ); return datepicker.regional.cs; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-cy-GB.js b/lib/web/jquery/ui-modules/i18n/datepicker-cy-GB.js index 14fce914c33eb..942c24f72dfe9 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-cy-GB.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-cy-GB.js @@ -1,6 +1,8 @@ /* Welsh/UK initialisation for the jQuery UI date picker plugin. */ /* Written by William Griffiths. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,15 +12,16 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "cy-GB" ] = { closeText: "Done", prevText: "Prev", nextText: "Next", currentText: "Today", - monthNames: [ "Ionawr","Chwefror","Mawrth","Ebrill","Mai","Mehefin", - "Gorffennaf","Awst","Medi","Hydref","Tachwedd","Rhagfyr" ], + monthNames: [ "Ionawr", "Chwefror", "Mawrth", "Ebrill", "Mai", "Mehefin", + "Gorffennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr" ], monthNamesShort: [ "Ion", "Chw", "Maw", "Ebr", "Mai", "Meh", "Gor", "Aws", "Med", "Hyd", "Tac", "Rha" ], dayNames: [ @@ -31,7 +34,7 @@ datepicker.regional[ "cy-GB" ] = { "Dydd Sadwrn" ], dayNamesShort: [ "Sul", "Llu", "Maw", "Mer", "Iau", "Gwe", "Sad" ], - dayNamesMin: [ "Su","Ll","Ma","Me","Ia","Gw","Sa" ], + dayNamesMin: [ "Su", "Ll", "Ma", "Me", "Ia", "Gw", "Sa" ], weekHeader: "Wy", dateFormat: "dd/mm/yy", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional[ "cy-GB" ] ); return datepicker.regional[ "cy-GB" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-da.js b/lib/web/jquery/ui-modules/i18n/datepicker-da.js index 273f0e3ff6ecd..90409c06efd73 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-da.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-da.js @@ -1,6 +1,8 @@ /* Danish initialisation for the jQuery UI date picker plugin. */ /* Written by Jan Christensen ( deletestuff@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.da = { closeText: "Luk", prevText: "<Forrige", nextText: "Næste>", - currentText: "Idag", - monthNames: [ "Januar","Februar","Marts","April","Maj","Juni", - "Juli","August","September","Oktober","November","December" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Aug","Sep","Okt","Nov","Dec" ], - dayNames: [ "Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag" ], - dayNamesShort: [ "Søn","Man","Tir","Ons","Tor","Fre","Lør" ], - dayNamesMin: [ "Sø","Ma","Ti","On","To","Fr","Lø" ], + currentText: "I dag", + monthNames: [ "Januar", "Februar", "Marts", "April", "Maj", "Juni", + "Juli", "August", "September", "Oktober", "November", "December" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag" ], + dayNamesShort: [ "Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør" ], + dayNamesMin: [ "Sø", "Ma", "Ti", "On", "To", "Fr", "Lø" ], weekHeader: "Uge", dateFormat: "dd-mm-yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.da ); return datepicker.regional.da; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-de-AT.js b/lib/web/jquery/ui-modules/i18n/datepicker-de-AT.js new file mode 100644 index 0000000000000..814e74f3eac45 --- /dev/null +++ b/lib/web/jquery/ui-modules/i18n/datepicker-de-AT.js @@ -0,0 +1,41 @@ +/* German/Austrian initialisation for the jQuery UI date picker plugin. */ +/* Based on the de initialisation. */ + +( function( factory ) { + "use strict"; + + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define( [ "../widgets/datepicker" ], factory ); + } else { + + // Browser globals + factory( jQuery.datepicker ); + } +} )( function( datepicker ) { +"use strict"; + +datepicker.regional[ "de-AT" ] = { + closeText: "Schließen", + prevText: "<Zurück", + nextText: "Vor>", + currentText: "Heute", + monthNames: [ "Jänner", "Februar", "März", "April", "Mai", "Juni", + "Juli", "August", "September", "Oktober", "November", "Dezember" ], + monthNamesShort: [ "Jän", "Feb", "Mär", "Apr", "Mai", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Dez" ], + dayNames: [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ], + dayNamesShort: [ "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" ], + dayNamesMin: [ "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" ], + weekHeader: "KW", + dateFormat: "dd.mm.yy", + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: "" }; +datepicker.setDefaults( datepicker.regional[ "de-AT" ] ); + +return datepicker.regional[ "de-AT" ]; + +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-de.js b/lib/web/jquery/ui-modules/i18n/datepicker-de.js index a67790844a9dd..5baf6d1486ff7 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-de.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-de.js @@ -1,6 +1,8 @@ /* German initialisation for the jQuery UI date picker plugin. */ /* Written by Milian Wolff (mail@milianw.de). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.de = { closeText: "Schließen", prevText: "<Zurück", nextText: "Vor>", currentText: "Heute", - monthNames: [ "Januar","Februar","März","April","Mai","Juni", - "Juli","August","September","Oktober","November","Dezember" ], - monthNamesShort: [ "Jan","Feb","Mär","Apr","Mai","Jun", - "Jul","Aug","Sep","Okt","Nov","Dez" ], - dayNames: [ "Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag" ], - dayNamesShort: [ "So","Mo","Di","Mi","Do","Fr","Sa" ], - dayNamesMin: [ "So","Mo","Di","Mi","Do","Fr","Sa" ], + monthNames: [ "Januar", "Februar", "März", "April", "Mai", "Juni", + "Juli", "August", "September", "Oktober", "November", "Dezember" ], + monthNamesShort: [ "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Dez" ], + dayNames: [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ], + dayNamesShort: [ "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" ], + dayNamesMin: [ "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" ], weekHeader: "KW", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.de ); return datepicker.regional.de; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-el.js b/lib/web/jquery/ui-modules/i18n/datepicker-el.js index f08d6f27d4296..054a5e2d9246a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-el.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-el.js @@ -1,6 +1,8 @@ /* Greek (el) initialisation for the jQuery UI date picker plugin. */ /* Written by Alex Cicovic (http://www.alexcicovic.com) */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.el = { closeText: "Κλείσιμο", prevText: "Προηγούμενος", nextText: "Επόμενος", currentText: "Σήμερα", - monthNames: [ "Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος", - "Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος" ], - monthNamesShort: [ "Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν", - "Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ" ], - dayNames: [ "Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο" ], - dayNamesShort: [ "Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ" ], - dayNamesMin: [ "Κυ","Δε","Τρ","Τε","Πε","Πα","Σα" ], + monthNames: [ "Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", + "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος" ], + monthNamesShort: [ "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαι", "Ιουν", + "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ" ], + dayNames: [ "Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο" ], + dayNamesShort: [ "Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ" ], + dayNamesMin: [ "Κυ", "Δε", "Τρ", "Τε", "Πε", "Πα", "Σα" ], weekHeader: "Εβδ", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.el ); return datepicker.regional.el; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-en-AU.js b/lib/web/jquery/ui-modules/i18n/datepicker-en-AU.js index f15277c37bc55..baef43a4885f4 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-en-AU.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-en-AU.js @@ -1,6 +1,8 @@ /* English/Australia initialisation for the jQuery UI date picker plugin. */ /* Based on the en-GB initialisation. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "en-AU" ] = { closeText: "Done", prevText: "Prev", nextText: "Next", currentText: "Today", - monthNames: [ "January","February","March","April","May","June", - "July","August","September","October","November","December" ], + monthNames: [ "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" ], monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], - dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], + dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "en-AU" ] ); return datepicker.regional[ "en-AU" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-en-GB.js b/lib/web/jquery/ui-modules/i18n/datepicker-en-GB.js index c961c18659e7b..e0a7a32aec042 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-en-GB.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-en-GB.js @@ -1,6 +1,8 @@ /* English/UK initialisation for the jQuery UI date picker plugin. */ /* Written by Stuart. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "en-GB" ] = { closeText: "Done", prevText: "Prev", nextText: "Next", currentText: "Today", - monthNames: [ "January","February","March","April","May","June", - "July","August","September","October","November","December" ], + monthNames: [ "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" ], monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], - dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], + dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "en-GB" ] ); return datepicker.regional[ "en-GB" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-en-NZ.js b/lib/web/jquery/ui-modules/i18n/datepicker-en-NZ.js index 70463622541aa..6e4768979e698 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-en-NZ.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-en-NZ.js @@ -1,6 +1,8 @@ /* English/New Zealand initialisation for the jQuery UI date picker plugin. */ /* Based on the en-GB initialisation. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "en-NZ" ] = { closeText: "Done", prevText: "Prev", nextText: "Next", currentText: "Today", - monthNames: [ "January","February","March","April","May","June", - "July","August","September","October","November","December" ], + monthNames: [ "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" ], monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], - dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], + dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "en-NZ" ] ); return datepicker.regional[ "en-NZ" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-eo.js b/lib/web/jquery/ui-modules/i18n/datepicker-eo.js index 25f6162b5cd6a..3867a6214f603 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-eo.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-eo.js @@ -1,6 +1,8 @@ /* Esperanto initialisation for the jQuery UI date picker plugin. */ /* Written by Olivier M. (olivierweb@ifrance.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.eo = { closeText: "Fermi", prevText: "<Anta", nextText: "Sekv>", currentText: "Nuna", - monthNames: [ "Januaro","Februaro","Marto","Aprilo","Majo","Junio", - "Julio","Aŭgusto","Septembro","Oktobro","Novembro","Decembro" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Aŭg","Sep","Okt","Nov","Dec" ], - dayNames: [ "Dimanĉo","Lundo","Mardo","Merkredo","Ĵaŭdo","Vendredo","Sabato" ], - dayNamesShort: [ "Dim","Lun","Mar","Mer","Ĵaŭ","Ven","Sab" ], - dayNamesMin: [ "Di","Lu","Ma","Me","Ĵa","Ve","Sa" ], + monthNames: [ "Januaro", "Februaro", "Marto", "Aprilo", "Majo", "Junio", + "Julio", "Aŭgusto", "Septembro", "Oktobro", "Novembro", "Decembro" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", + "Jul", "Aŭg", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "Dimanĉo", "Lundo", "Mardo", "Merkredo", "Ĵaŭdo", "Vendredo", "Sabato" ], + dayNamesShort: [ "Dim", "Lun", "Mar", "Mer", "Ĵaŭ", "Ven", "Sab" ], + dayNamesMin: [ "Di", "Lu", "Ma", "Me", "Ĵa", "Ve", "Sa" ], weekHeader: "Sb", dateFormat: "dd/mm/yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.eo ); return datepicker.regional.eo; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-es.js b/lib/web/jquery/ui-modules/i18n/datepicker-es.js index ea7116e0bfd62..9a7457778c493 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-es.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-es.js @@ -1,6 +1,8 @@ /* Inicialización en español para la extensión 'UI date picker' para jQuery. */ /* Traducido por Vester (xvester@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.es = { closeText: "Cerrar", prevText: "<Ant", nextText: "Sig>", currentText: "Hoy", - monthNames: [ "enero","febrero","marzo","abril","mayo","junio", - "julio","agosto","septiembre","octubre","noviembre","diciembre" ], - monthNamesShort: [ "ene","feb","mar","abr","may","jun", - "jul","ago","sep","oct","nov","dic" ], - dayNames: [ "domingo","lunes","martes","miércoles","jueves","viernes","sábado" ], - dayNamesShort: [ "dom","lun","mar","mié","jue","vie","sáb" ], - dayNamesMin: [ "D","L","M","X","J","V","S" ], + monthNames: [ "enero", "febrero", "marzo", "abril", "mayo", "junio", + "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre" ], + monthNamesShort: [ "ene", "feb", "mar", "abr", "may", "jun", + "jul", "ago", "sep", "oct", "nov", "dic" ], + dayNames: [ "domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado" ], + dayNamesShort: [ "dom", "lun", "mar", "mié", "jue", "vie", "sáb" ], + dayNamesMin: [ "D", "L", "M", "X", "J", "V", "S" ], weekHeader: "Sm", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.es ); return datepicker.regional.es; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-et.js b/lib/web/jquery/ui-modules/i18n/datepicker-et.js index b2e226ae54fab..11d58579efc75 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-et.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-et.js @@ -1,6 +1,8 @@ /* Estonian initialisation for the jQuery UI date picker plugin. */ /* Written by Mart Sõmermaa (mrts.pydev at gmail com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,15 +12,16 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.et = { closeText: "Sulge", prevText: "Eelnev", nextText: "Järgnev", currentText: "Täna", - monthNames: [ "Jaanuar","Veebruar","Märts","Aprill","Mai","Juuni", - "Juuli","August","September","Oktoober","November","Detsember" ], + monthNames: [ "Jaanuar", "Veebruar", "Märts", "Aprill", "Mai", "Juuni", + "Juuli", "August", "September", "Oktoober", "November", "Detsember" ], monthNamesShort: [ "Jaan", "Veebr", "Märts", "Apr", "Mai", "Juuni", "Juuli", "Aug", "Sept", "Okt", "Nov", "Dets" ], dayNames: [ @@ -31,7 +34,7 @@ datepicker.regional.et = { "Laupäev" ], dayNamesShort: [ "Pühap", "Esmasp", "Teisip", "Kolmap", "Neljap", "Reede", "Laup" ], - dayNamesMin: [ "P","E","T","K","N","R","L" ], + dayNamesMin: [ "P", "E", "T", "K", "N", "R", "L" ], weekHeader: "näd", dateFormat: "dd.mm.yy", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.et ); return datepicker.regional.et; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-eu.js b/lib/web/jquery/ui-modules/i18n/datepicker-eu.js index 8ea1ef9e584f1..754a172df5a7d 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-eu.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-eu.js @@ -1,5 +1,7 @@ /* Karrikas-ek itzulia (karrikas@karrikas.com) */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -9,20 +11,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.eu = { closeText: "Egina", prevText: "<Aur", nextText: "Hur>", currentText: "Gaur", - monthNames: [ "urtarrila","otsaila","martxoa","apirila","maiatza","ekaina", - "uztaila","abuztua","iraila","urria","azaroa","abendua" ], - monthNamesShort: [ "urt.","ots.","mar.","api.","mai.","eka.", - "uzt.","abu.","ira.","urr.","aza.","abe." ], - dayNames: [ "igandea","astelehena","asteartea","asteazkena","osteguna","ostirala","larunbata" ], - dayNamesShort: [ "ig.","al.","ar.","az.","og.","ol.","lr." ], - dayNamesMin: [ "ig","al","ar","az","og","ol","lr" ], + monthNames: [ "urtarrila", "otsaila", "martxoa", "apirila", "maiatza", "ekaina", + "uztaila", "abuztua", "iraila", "urria", "azaroa", "abendua" ], + monthNamesShort: [ "urt.", "ots.", "mar.", "api.", "mai.", "eka.", + "uzt.", "abu.", "ira.", "urr.", "aza.", "abe." ], + dayNames: [ "igandea", "astelehena", "asteartea", "asteazkena", "osteguna", "ostirala", "larunbata" ], + dayNamesShort: [ "ig.", "al.", "ar.", "az.", "og.", "ol.", "lr." ], + dayNamesMin: [ "ig", "al", "ar", "az", "og", "ol", "lr" ], weekHeader: "As", dateFormat: "yy-mm-dd", firstDay: 1, @@ -33,4 +36,4 @@ datepicker.setDefaults( datepicker.regional.eu ); return datepicker.regional.eu; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fa.js b/lib/web/jquery/ui-modules/i18n/datepicker-fa.js index 71da4981d3db2..193a3dcc1ed40 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fa.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fa.js @@ -2,6 +2,8 @@ /* Javad Mowlanezhad -- jmowla@gmail.com */ /* Jalali calendar should supported soon! (Its implemented but I have to test it) */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -11,7 +13,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.fa = { closeText: "بستن", @@ -32,7 +35,7 @@ datepicker.regional.fa = { "نوامبر", "دسامبر" ], - monthNamesShort: [ "1","2","3","4","5","6","7","8","9","10","11","12" ], + monthNamesShort: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" ], dayNames: [ "يکشنبه", "دوشنبه", @@ -70,4 +73,4 @@ datepicker.setDefaults( datepicker.regional.fa ); return datepicker.regional.fa; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fi.js b/lib/web/jquery/ui-modules/i18n/datepicker-fi.js index a8386ff626776..dd4ca5944a76f 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fi.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fi.js @@ -1,6 +1,8 @@ /* Finnish initialisation for the jQuery UI date picker plugin. */ /* Written by Harri Kilpiö (harrikilpio@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.fi = { closeText: "Sulje", prevText: "«Edellinen", nextText: "Seuraava»", currentText: "Tänään", - monthNames: [ "Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu", - "Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu" ], - monthNamesShort: [ "Tammi","Helmi","Maalis","Huhti","Touko","Kesä", - "Heinä","Elo","Syys","Loka","Marras","Joulu" ], - dayNamesShort: [ "Su","Ma","Ti","Ke","To","Pe","La" ], - dayNames: [ "Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai" ], - dayNamesMin: [ "Su","Ma","Ti","Ke","To","Pe","La" ], + monthNames: [ "Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", + "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu" ], + monthNamesShort: [ "Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", + "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu" ], + dayNamesShort: [ "Su", "Ma", "Ti", "Ke", "To", "Pe", "La" ], + dayNames: [ "Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai" ], + dayNamesMin: [ "Su", "Ma", "Ti", "Ke", "To", "Pe", "La" ], weekHeader: "Vk", dateFormat: "d.m.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.fi ); return datepicker.regional.fi; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fo.js b/lib/web/jquery/ui-modules/i18n/datepicker-fo.js index 6c24b8bff808e..f024ac6742525 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fo.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fo.js @@ -1,6 +1,8 @@ /* Faroese initialisation for the jQuery UI date picker plugin */ /* Written by Sverri Mohr Olsen, sverrimo@gmail.com */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.fo = { closeText: "Lat aftur", prevText: "<Fyrra", nextText: "Næsta>", currentText: "Í dag", - monthNames: [ "Januar","Februar","Mars","Apríl","Mei","Juni", - "Juli","August","September","Oktober","November","Desember" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Mei","Jun", - "Jul","Aug","Sep","Okt","Nov","Des" ], + monthNames: [ "Januar", "Februar", "Mars", "Apríl", "Mei", "Juni", + "Juli", "August", "September", "Oktober", "November", "Desember" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Des" ], dayNames: [ "Sunnudagur", "Mánadagur", @@ -30,8 +33,8 @@ datepicker.regional.fo = { "Fríggjadagur", "Leyardagur" ], - dayNamesShort: [ "Sun","Mán","Týs","Mik","Hós","Frí","Ley" ], - dayNamesMin: [ "Su","Má","Tý","Mi","Hó","Fr","Le" ], + dayNamesShort: [ "Sun", "Mán", "Týs", "Mik", "Hós", "Frí", "Ley" ], + dayNamesMin: [ "Su", "Má", "Tý", "Mi", "Hó", "Fr", "Le" ], weekHeader: "Vk", dateFormat: "dd-mm-yy", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.fo ); return datepicker.regional.fo; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fr-CA.js b/lib/web/jquery/ui-modules/i18n/datepicker-fr-CA.js index b590277d4b087..a14b1d323b2da 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fr-CA.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fr-CA.js @@ -1,5 +1,7 @@ /* Canadian-French initialisation for the jQuery UI date picker plugin. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -9,7 +11,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "fr-CA" ] = { closeText: "Fermer", @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "fr-CA" ] ); return datepicker.regional[ "fr-CA" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fr-CH.js b/lib/web/jquery/ui-modules/i18n/datepicker-fr-CH.js index d2f0584d6a86e..b75c683379a25 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fr-CH.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fr-CH.js @@ -1,6 +1,8 @@ /* Swiss-French initialisation for the jQuery UI date picker plugin. */ /* Written Martin Voelkle (martin.voelkle@e-tc.ch). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "fr-CH" ] = { closeText: "Fermer", @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "fr-CH" ] ); return datepicker.regional[ "fr-CH" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-fr.js b/lib/web/jquery/ui-modules/i18n/datepicker-fr.js index 9e39fbd68f56d..42b582bc30003 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-fr.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-fr.js @@ -3,6 +3,8 @@ Stéphane Nahmani (sholby@sholby.net), Stéphane Raimbault <stephane.raimbault@gmail.com> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -12,7 +14,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.fr = { closeText: "Fermer", @@ -25,7 +28,7 @@ datepicker.regional.fr = { "juil.", "août", "sept.", "oct.", "nov.", "déc." ], dayNames: [ "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" ], dayNamesShort: [ "dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam." ], - dayNamesMin: [ "D","L","M","M","J","V","S" ], + dayNamesMin: [ "D", "L", "M", "M", "J", "V", "S" ], weekHeader: "Sem.", dateFormat: "dd/mm/yy", firstDay: 1, @@ -36,4 +39,4 @@ datepicker.setDefaults( datepicker.regional.fr ); return datepicker.regional.fr; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-gl.js b/lib/web/jquery/ui-modules/i18n/datepicker-gl.js index 2765230746e66..f3ebc46651e94 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-gl.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-gl.js @@ -1,6 +1,8 @@ /* Galician localization for 'UI date picker' jQuery extension. */ /* Translated by Jorge Barreiro <yortx.barry@gmail.com>. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.gl = { closeText: "Pechar", prevText: "<Ant", nextText: "Seg>", currentText: "Hoxe", - monthNames: [ "Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño", - "Xullo","Agosto","Setembro","Outubro","Novembro","Decembro" ], - monthNamesShort: [ "Xan","Feb","Mar","Abr","Mai","Xuñ", - "Xul","Ago","Set","Out","Nov","Dec" ], - dayNames: [ "Domingo","Luns","Martes","Mércores","Xoves","Venres","Sábado" ], - dayNamesShort: [ "Dom","Lun","Mar","Mér","Xov","Ven","Sáb" ], - dayNamesMin: [ "Do","Lu","Ma","Mé","Xo","Ve","Sá" ], + monthNames: [ "Xaneiro", "Febreiro", "Marzo", "Abril", "Maio", "Xuño", + "Xullo", "Agosto", "Setembro", "Outubro", "Novembro", "Decembro" ], + monthNamesShort: [ "Xan", "Feb", "Mar", "Abr", "Mai", "Xuñ", + "Xul", "Ago", "Set", "Out", "Nov", "Dec" ], + dayNames: [ "Domingo", "Luns", "Martes", "Mércores", "Xoves", "Venres", "Sábado" ], + dayNamesShort: [ "Dom", "Lun", "Mar", "Mér", "Xov", "Ven", "Sáb" ], + dayNamesMin: [ "Do", "Lu", "Ma", "Mé", "Xo", "Ve", "Sá" ], weekHeader: "Sm", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.gl ); return datepicker.regional.gl; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-he.js b/lib/web/jquery/ui-modules/i18n/datepicker-he.js index fb6238fdac779..c487e78f70b46 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-he.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-he.js @@ -1,6 +1,8 @@ /* Hebrew initialisation for the UI Datepicker extension. */ /* Written by Amir Hardon (ahardon at gmail dot com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.he = { closeText: "סגור", prevText: "<הקודם", nextText: "הבא>", currentText: "היום", - monthNames: [ "ינואר","פברואר","מרץ","אפריל","מאי","יוני", - "יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר" ], - monthNamesShort: [ "ינו","פבר","מרץ","אפר","מאי","יוני", - "יולי","אוג","ספט","אוק","נוב","דצמ" ], - dayNames: [ "ראשון","שני","שלישי","רביעי","חמישי","שישי","שבת" ], - dayNamesShort: [ "א'","ב'","ג'","ד'","ה'","ו'","שבת" ], - dayNamesMin: [ "א'","ב'","ג'","ד'","ה'","ו'","שבת" ], + monthNames: [ "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", + "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר" ], + monthNamesShort: [ "ינו", "פבר", "מרץ", "אפר", "מאי", "יוני", + "יולי", "אוג", "ספט", "אוק", "נוב", "דצמ" ], + dayNames: [ "ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת" ], + dayNamesShort: [ "א'", "ב'", "ג'", "ד'", "ה'", "ו'", "שבת" ], + dayNamesMin: [ "א'", "ב'", "ג'", "ד'", "ה'", "ו'", "שבת" ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.he ); return datepicker.regional.he; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-hi.js b/lib/web/jquery/ui-modules/i18n/datepicker-hi.js index 3b120972459d5..e3b72e52b2fb0 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-hi.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-hi.js @@ -1,6 +1,8 @@ /* Hindi initialisation for the jQuery UI date picker plugin. */ /* Written by Michael Dawart. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,15 +12,16 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.hi = { closeText: "बंद", prevText: "पिछला", nextText: "अगला", currentText: "आज", - monthNames: [ "जनवरी ","फरवरी","मार्च","अप्रेल","मई","जून", - "जूलाई","अगस्त ","सितम्बर","अक्टूबर","नवम्बर","दिसम्बर" ], + monthNames: [ "जनवरी ", "फरवरी", "मार्च", "अप्रेल", "मई", "जून", + "जूलाई", "अगस्त ", "सितम्बर", "अक्टूबर", "नवम्बर", "दिसम्बर" ], monthNamesShort: [ "जन", "फर", "मार्च", "अप्रेल", "मई", "जून", "जूलाई", "अग", "सित", "अक्ट", "नव", "दि" ], dayNames: [ "रविवार", "सोमवार", "मंगलवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार" ], @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.hi ); return datepicker.regional.hi; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-hr.js b/lib/web/jquery/ui-modules/i18n/datepicker-hr.js index 5e218c12d8a1e..23ea41446ed93 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-hr.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-hr.js @@ -1,6 +1,8 @@ /* Croatian i18n for the jQuery UI date picker plugin. */ /* Written by Vjekoslav Nesek. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.hr = { closeText: "Zatvori", prevText: "<", nextText: ">", currentText: "Danas", - monthNames: [ "Siječanj","Veljača","Ožujak","Travanj","Svibanj","Lipanj", - "Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac" ], - monthNamesShort: [ "Sij","Velj","Ožu","Tra","Svi","Lip", - "Srp","Kol","Ruj","Lis","Stu","Pro" ], - dayNames: [ "Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota" ], - dayNamesShort: [ "Ned","Pon","Uto","Sri","Čet","Pet","Sub" ], - dayNamesMin: [ "Ne","Po","Ut","Sr","Če","Pe","Su" ], + monthNames: [ "Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", + "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac" ], + monthNamesShort: [ "Sij", "Velj", "Ožu", "Tra", "Svi", "Lip", + "Srp", "Kol", "Ruj", "Lis", "Stu", "Pro" ], + dayNames: [ "Nedjelja", "Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota" ], + dayNamesShort: [ "Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub" ], + dayNamesMin: [ "Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su" ], weekHeader: "Tje", dateFormat: "dd.mm.yy.", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.hr ); return datepicker.regional.hr; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-hu.js b/lib/web/jquery/ui-modules/i18n/datepicker-hu.js index 22bbe7031e9e2..3bb86dab9d48b 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-hu.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-hu.js @@ -1,5 +1,7 @@ /* Hungarian initialisation for the jQuery UI date picker plugin. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -9,13 +11,14 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.hu = { - closeText: "bezár", - prevText: "vissza", - nextText: "előre", - currentText: "ma", + closeText: "Bezár", + prevText: "Vissza", + nextText: "Előre", + currentText: "Ma", monthNames: [ "Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December" ], monthNamesShort: [ "Jan", "Feb", "Már", "Ápr", "Máj", "Jún", @@ -33,4 +36,4 @@ datepicker.setDefaults( datepicker.regional.hu ); return datepicker.regional.hu; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-hy.js b/lib/web/jquery/ui-modules/i18n/datepicker-hy.js index 95638b31078d1..2cc74da734cc8 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-hy.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-hy.js @@ -1,6 +1,8 @@ /* Armenian(UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Levon Zakaryan (levon.zakaryan@gmail.com)*/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.hy = { closeText: "Փակել", prevText: "<Նախ.", nextText: "Հաջ.>", currentText: "Այսօր", - monthNames: [ "Հունվար","Փետրվար","Մարտ","Ապրիլ","Մայիս","Հունիս", - "Հուլիս","Օգոստոս","Սեպտեմբեր","Հոկտեմբեր","Նոյեմբեր","Դեկտեմբեր" ], - monthNamesShort: [ "Հունվ","Փետր","Մարտ","Ապր","Մայիս","Հունիս", - "Հուլ","Օգս","Սեպ","Հոկ","Նոյ","Դեկ" ], - dayNames: [ "կիրակի","եկուշաբթի","երեքշաբթի","չորեքշաբթի","հինգշաբթի","ուրբաթ","շաբաթ" ], - dayNamesShort: [ "կիր","երկ","երք","չրք","հնգ","ուրբ","շբթ" ], - dayNamesMin: [ "կիր","երկ","երք","չրք","հնգ","ուրբ","շբթ" ], + monthNames: [ "Հունվար", "Փետրվար", "Մարտ", "Ապրիլ", "Մայիս", "Հունիս", + "Հուլիս", "Օգոստոս", "Սեպտեմբեր", "Հոկտեմբեր", "Նոյեմբեր", "Դեկտեմբեր" ], + monthNamesShort: [ "Հունվ", "Փետր", "Մարտ", "Ապր", "Մայիս", "Հունիս", + "Հուլ", "Օգս", "Սեպ", "Հոկ", "Նոյ", "Դեկ" ], + dayNames: [ "կիրակի", "եկուշաբթի", "երեքշաբթի", "չորեքշաբթի", "հինգշաբթի", "ուրբաթ", "շաբաթ" ], + dayNamesShort: [ "կիր", "երկ", "երք", "չրք", "հնգ", "ուրբ", "շբթ" ], + dayNamesMin: [ "կիր", "երկ", "երք", "չրք", "հնգ", "ուրբ", "շբթ" ], weekHeader: "ՇԲՏ", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.hy ); return datepicker.regional.hy; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-id.js b/lib/web/jquery/ui-modules/i18n/datepicker-id.js index 5aef348af8540..52f709caa5bb7 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-id.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-id.js @@ -1,6 +1,8 @@ /* Indonesian initialisation for the jQuery UI date picker plugin. */ /* Written by Deden Fathurahman (dedenf@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.id = { closeText: "Tutup", prevText: "<mundur", nextText: "maju>", currentText: "hari ini", - monthNames: [ "Januari","Februari","Maret","April","Mei","Juni", - "Juli","Agustus","September","Oktober","Nopember","Desember" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Mei","Jun", - "Jul","Agus","Sep","Okt","Nop","Des" ], - dayNames: [ "Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu" ], - dayNamesShort: [ "Min","Sen","Sel","Rab","kam","Jum","Sab" ], - dayNamesMin: [ "Mg","Sn","Sl","Rb","Km","jm","Sb" ], + monthNames: [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", + "Juli", "Agustus", "September", "Oktober", "Nopember", "Desember" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", + "Jul", "Agus", "Sep", "Okt", "Nop", "Des" ], + dayNames: [ "Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu" ], + dayNamesShort: [ "Min", "Sen", "Sel", "Rab", "kam", "Jum", "Sab" ], + dayNamesMin: [ "Mg", "Sn", "Sl", "Rb", "Km", "jm", "Sb" ], weekHeader: "Mg", dateFormat: "dd/mm/yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.id ); return datepicker.regional.id; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-is.js b/lib/web/jquery/ui-modules/i18n/datepicker-is.js index b15f37ab06cd7..0ebffd35541b7 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-is.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-is.js @@ -1,6 +1,8 @@ /* Icelandic initialisation for the jQuery UI date picker plugin. */ /* Written by Haukur H. Thorsson (haukur@eskill.is). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.is = { closeText: "Loka", prevText: "< Fyrri", nextText: "Næsti >", currentText: "Í dag", - monthNames: [ "Janúar","Febrúar","Mars","Apríl","Maí","Júní", - "Júlí","Ágúst","September","Október","Nóvember","Desember" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maí","Jún", - "Júl","Ágú","Sep","Okt","Nóv","Des" ], + monthNames: [ "Janúar", "Febrúar", "Mars", "Apríl", "Maí", "Júní", + "Júlí", "Ágúst", "September", "Október", "Nóvember", "Desember" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maí", "Jún", + "Júl", "Ágú", "Sep", "Okt", "Nóv", "Des" ], dayNames: [ "Sunnudagur", "Mánudagur", @@ -30,8 +33,8 @@ datepicker.regional.is = { "Föstudagur", "Laugardagur" ], - dayNamesShort: [ "Sun","Mán","Þri","Mið","Fim","Fös","Lau" ], - dayNamesMin: [ "Su","Má","Þr","Mi","Fi","Fö","La" ], + dayNamesShort: [ "Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau" ], + dayNamesMin: [ "Su", "Má", "Þr", "Mi", "Fi", "Fö", "La" ], weekHeader: "Vika", dateFormat: "dd.mm.yy", firstDay: 0, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.is ); return datepicker.regional.is; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-it-CH.js b/lib/web/jquery/ui-modules/i18n/datepicker-it-CH.js index 9895da4cc3215..8c6d1058010b1 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-it-CH.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-it-CH.js @@ -1,6 +1,8 @@ /* Italian initialisation for the jQuery UI date picker plugin. */ /* Written by Antonello Pasella (antonello.pasella@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "it-CH" ] = { closeText: "Chiudi", prevText: "<Prec", nextText: "Succ>", currentText: "Oggi", - monthNames: [ "Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno", - "Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre" ], - monthNamesShort: [ "Gen","Feb","Mar","Apr","Mag","Giu", - "Lug","Ago","Set","Ott","Nov","Dic" ], - dayNames: [ "Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato" ], - dayNamesShort: [ "Dom","Lun","Mar","Mer","Gio","Ven","Sab" ], - dayNamesMin: [ "Do","Lu","Ma","Me","Gi","Ve","Sa" ], + monthNames: [ "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", + "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre" ], + monthNamesShort: [ "Gen", "Feb", "Mar", "Apr", "Mag", "Giu", + "Lug", "Ago", "Set", "Ott", "Nov", "Dic" ], + dayNames: [ "Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato" ], + dayNamesShort: [ "Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab" ], + dayNamesMin: [ "Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa" ], weekHeader: "Sm", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "it-CH" ] ); return datepicker.regional[ "it-CH" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-it.js b/lib/web/jquery/ui-modules/i18n/datepicker-it.js index d67cb6c248a01..0210e41c49cb6 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-it.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-it.js @@ -1,6 +1,8 @@ /* Italian initialisation for the jQuery UI date picker plugin. */ /* Written by Antonello Pasella (antonello.pasella@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.it = { closeText: "Chiudi", prevText: "<Prec", nextText: "Succ>", currentText: "Oggi", - monthNames: [ "Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno", - "Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre" ], - monthNamesShort: [ "Gen","Feb","Mar","Apr","Mag","Giu", - "Lug","Ago","Set","Ott","Nov","Dic" ], - dayNames: [ "Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato" ], - dayNamesShort: [ "Dom","Lun","Mar","Mer","Gio","Ven","Sab" ], - dayNamesMin: [ "Do","Lu","Ma","Me","Gi","Ve","Sa" ], + monthNames: [ "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", + "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre" ], + monthNamesShort: [ "Gen", "Feb", "Mar", "Apr", "Mag", "Giu", + "Lug", "Ago", "Set", "Ott", "Nov", "Dic" ], + dayNames: [ "Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato" ], + dayNamesShort: [ "Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab" ], + dayNamesMin: [ "Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa" ], weekHeader: "Sm", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.it ); return datepicker.regional.it; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ja.js b/lib/web/jquery/ui-modules/i18n/datepicker-ja.js index 52b10583cabe1..404bbfd64b628 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ja.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ja.js @@ -1,6 +1,8 @@ /* Japanese initialisation for the jQuery UI date picker plugin. */ /* Written by Kentaro SATO (kentaro@ranvis.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ja = { closeText: "閉じる", prevText: "<前", nextText: "次>", currentText: "今日", - monthNames: [ "1月","2月","3月","4月","5月","6月", - "7月","8月","9月","10月","11月","12月" ], - monthNamesShort: [ "1月","2月","3月","4月","5月","6月", - "7月","8月","9月","10月","11月","12月" ], - dayNames: [ "日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日" ], - dayNamesShort: [ "日","月","火","水","木","金","土" ], - dayNamesMin: [ "日","月","火","水","木","金","土" ], + monthNames: [ "1月", "2月", "3月", "4月", "5月", "6月", + "7月", "8月", "9月", "10月", "11月", "12月" ], + monthNamesShort: [ "1月", "2月", "3月", "4月", "5月", "6月", + "7月", "8月", "9月", "10月", "11月", "12月" ], + dayNames: [ "日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日" ], + dayNamesShort: [ "日", "月", "火", "水", "木", "金", "土" ], + dayNamesMin: [ "日", "月", "火", "水", "木", "金", "土" ], weekHeader: "週", dateFormat: "yy/mm/dd", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ja ); return datepicker.regional.ja; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ka.js b/lib/web/jquery/ui-modules/i18n/datepicker-ka.js index 1f596cb31867a..fd740dadea700 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ka.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ka.js @@ -1,6 +1,8 @@ /* Georgian (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Lado Lomidze (lado.lomidze@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ka = { closeText: "დახურვა", @@ -31,10 +34,10 @@ datepicker.regional.ka = { "ნოემბერი", "დეკემბერი" ], - monthNamesShort: [ "იან","თებ","მარ","აპრ","მაი","ივნ", "ივლ","აგვ","სექ","ოქტ","ნოე","დეკ" ], - dayNames: [ "კვირა","ორშაბათი","სამშაბათი","ოთხშაბათი","ხუთშაბათი","პარასკევი","შაბათი" ], - dayNamesShort: [ "კვ","ორშ","სამ","ოთხ","ხუთ","პარ","შაბ" ], - dayNamesMin: [ "კვ","ორშ","სამ","ოთხ","ხუთ","პარ","შაბ" ], + monthNamesShort: [ "იან", "თებ", "მარ", "აპრ", "მაი", "ივნ", "ივლ", "აგვ", "სექ", "ოქტ", "ნოე", "დეკ" ], + dayNames: [ "კვირა", "ორშაბათი", "სამშაბათი", "ოთხშაბათი", "ხუთშაბათი", "პარასკევი", "შაბათი" ], + dayNamesShort: [ "კვ", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ" ], + dayNamesMin: [ "კვ", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ" ], weekHeader: "კვირა", dateFormat: "dd-mm-yy", firstDay: 1, @@ -45,4 +48,4 @@ datepicker.setDefaults( datepicker.regional.ka ); return datepicker.regional.ka; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-kk.js b/lib/web/jquery/ui-modules/i18n/datepicker-kk.js index fa0121f8c2a19..c40391d11a696 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-kk.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-kk.js @@ -1,6 +1,8 @@ /* Kazakh (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Dmitriy Karasyov (dmitriy.karasyov@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.kk = { closeText: "Жабу", prevText: "<Алдыңғы", nextText: "Келесі>", currentText: "Бүгін", - monthNames: [ "Қаңтар","Ақпан","Наурыз","Сәуір","Мамыр","Маусым", - "Шілде","Тамыз","Қыркүйек","Қазан","Қараша","Желтоқсан" ], - monthNamesShort: [ "Қаң","Ақп","Нау","Сәу","Мам","Мау", - "Шіл","Там","Қыр","Қаз","Қар","Жел" ], - dayNames: [ "Жексенбі","Дүйсенбі","Сейсенбі","Сәрсенбі","Бейсенбі","Жұма","Сенбі" ], - dayNamesShort: [ "жкс","дсн","ссн","срс","бсн","жма","снб" ], - dayNamesMin: [ "Жк","Дс","Сс","Ср","Бс","Жм","Сн" ], + monthNames: [ "Қаңтар", "Ақпан", "Наурыз", "Сәуір", "Мамыр", "Маусым", + "Шілде", "Тамыз", "Қыркүйек", "Қазан", "Қараша", "Желтоқсан" ], + monthNamesShort: [ "Қаң", "Ақп", "Нау", "Сәу", "Мам", "Мау", + "Шіл", "Там", "Қыр", "Қаз", "Қар", "Жел" ], + dayNames: [ "Жексенбі", "Дүйсенбі", "Сейсенбі", "Сәрсенбі", "Бейсенбі", "Жұма", "Сенбі" ], + dayNamesShort: [ "жкс", "дсн", "ссн", "срс", "бсн", "жма", "снб" ], + dayNamesMin: [ "Жк", "Дс", "Сс", "Ср", "Бс", "Жм", "Сн" ], weekHeader: "Не", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.kk ); return datepicker.regional.kk; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-km.js b/lib/web/jquery/ui-modules/i18n/datepicker-km.js index d8a4596bce76b..ab3d89a1e302d 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-km.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-km.js @@ -1,6 +1,8 @@ /* Khmer initialisation for the jQuery calendar extension. */ /* Written by Chandara Om (chandara.teacher@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.km = { closeText: "ធ្វើ​រួច", prevText: "មុន", nextText: "បន្ទាប់", currentText: "ថ្ងៃ​នេះ", - monthNames: [ "មករា","កុម្ភៈ","មីនា","មេសា","ឧសភា","មិថុនា", - "កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ" ], - monthNamesShort: [ "មករា","កុម្ភៈ","មីនា","មេសា","ឧសភា","មិថុនា", - "កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ" ], + monthNames: [ "មករា", "កុម្ភៈ", "មីនា", "មេសា", "ឧសភា", "មិថុនា", + "កក្កដា", "សីហា", "កញ្ញា", "តុលា", "វិច្ឆិកា", "ធ្នូ" ], + monthNamesShort: [ "មករា", "កុម្ភៈ", "មីនា", "មេសា", "ឧសភា", "មិថុនា", + "កក្កដា", "សីហា", "កញ្ញា", "តុលា", "វិច្ឆិកា", "ធ្នូ" ], dayNames: [ "អាទិត្យ", "ចន្ទ", "អង្គារ", "ពុធ", "ព្រហស្បតិ៍", "សុក្រ", "សៅរ៍" ], dayNamesShort: [ "អា", "ច", "អ", "ពុ", "ព្រហ", "សុ", "សៅ" ], dayNamesMin: [ "អា", "ច", "អ", "ពុ", "ព្រហ", "សុ", "សៅ" ], @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.km ); return datepicker.regional.km; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ko.js b/lib/web/jquery/ui-modules/i18n/datepicker-ko.js index 8879a99509040..deb1475e2303a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ko.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ko.js @@ -1,6 +1,8 @@ /* Korean initialisation for the jQuery calendar extension. */ /* Written by DaeKwon Kang (ncrash.dk@gmail.com), Edited by Genie and Myeongjin Lee. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ko = { closeText: "닫기", prevText: "이전달", nextText: "다음달", currentText: "오늘", - monthNames: [ "1월","2월","3월","4월","5월","6월", - "7월","8월","9월","10월","11월","12월" ], - monthNamesShort: [ "1월","2월","3월","4월","5월","6월", - "7월","8월","9월","10월","11월","12월" ], - dayNames: [ "일요일","월요일","화요일","수요일","목요일","금요일","토요일" ], - dayNamesShort: [ "일","월","화","수","목","금","토" ], - dayNamesMin: [ "일","월","화","수","목","금","토" ], + monthNames: [ "1월", "2월", "3월", "4월", "5월", "6월", + "7월", "8월", "9월", "10월", "11월", "12월" ], + monthNamesShort: [ "1월", "2월", "3월", "4월", "5월", "6월", + "7월", "8월", "9월", "10월", "11월", "12월" ], + dayNames: [ "일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일" ], + dayNamesShort: [ "일", "월", "화", "수", "목", "금", "토" ], + dayNamesMin: [ "일", "월", "화", "수", "목", "금", "토" ], weekHeader: "주", dateFormat: "yy. m. d.", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ko ); return datepicker.regional.ko; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ky.js b/lib/web/jquery/ui-modules/i18n/datepicker-ky.js index f748bc6060720..e74c92740400e 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ky.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ky.js @@ -1,6 +1,8 @@ /* Kyrgyz (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Sergey Kartashov (ebishkek@yandex.ru). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ky = { closeText: "Жабуу", prevText: "<Мур", nextText: "Кий>", currentText: "Бүгүн", - monthNames: [ "Январь","Февраль","Март","Апрель","Май","Июнь", - "Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь" ], - monthNamesShort: [ "Янв","Фев","Мар","Апр","Май","Июн", - "Июл","Авг","Сен","Окт","Ноя","Дек" ], + monthNames: [ "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", + "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" ], + monthNamesShort: [ "Янв", "Фев", "Мар", "Апр", "Май", "Июн", + "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" ], dayNames: [ "жекшемби", "дүйшөмбү", "шейшемби", "шаршемби", "бейшемби", "жума", "ишемби" ], dayNamesShort: [ "жек", "дүй", "шей", "шар", "бей", "жум", "ише" ], - dayNamesMin: [ "Жк","Дш","Шш","Шр","Бш","Жм","Иш" ], + dayNamesMin: [ "Жк", "Дш", "Шш", "Шр", "Бш", "Жм", "Иш" ], weekHeader: "Жум", dateFormat: "dd.mm.yy", firstDay: 1, @@ -35,4 +38,4 @@ datepicker.setDefaults( datepicker.regional.ky ); return datepicker.regional.ky; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-lb.js b/lib/web/jquery/ui-modules/i18n/datepicker-lb.js index 02a9c51082bfe..936eb441c1649 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-lb.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-lb.js @@ -1,6 +1,8 @@ /* Luxembourgish initialisation for the jQuery UI date picker plugin. */ /* Written by Michel Weimerskirch <michel@weimerskirch.net> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,15 +12,16 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.lb = { closeText: "Fäerdeg", prevText: "Zréck", nextText: "Weider", currentText: "Haut", - monthNames: [ "Januar","Februar","Mäerz","Abrëll","Mee","Juni", - "Juli","August","September","Oktober","November","Dezember" ], + monthNames: [ "Januar", "Februar", "Mäerz", "Abrëll", "Mee", "Juni", + "Juli", "August", "September", "Oktober", "November", "Dezember" ], monthNamesShort: [ "Jan", "Feb", "Mäe", "Abr", "Mee", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez" ], dayNames: [ @@ -31,7 +34,7 @@ datepicker.regional.lb = { "Samschdeg" ], dayNamesShort: [ "Son", "Méi", "Dën", "Mët", "Don", "Fre", "Sam" ], - dayNamesMin: [ "So","Mé","Dë","Më","Do","Fr","Sa" ], + dayNamesMin: [ "So", "Mé", "Dë", "Më", "Do", "Fr", "Sa" ], weekHeader: "W", dateFormat: "dd.mm.yy", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.lb ); return datepicker.regional.lb; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-lt.js b/lib/web/jquery/ui-modules/i18n/datepicker-lt.js index a57fd9df00b74..279af0cf7a950 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-lt.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-lt.js @@ -1,6 +1,8 @@ /* Lithuanian (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* @author Arturas Paleicikas <arturas@avalon.lt> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.lt = { closeText: "Uždaryti", prevText: "<Atgal", nextText: "Pirmyn>", currentText: "Šiandien", - monthNames: [ "Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis", - "Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis" ], - monthNamesShort: [ "Sau","Vas","Kov","Bal","Geg","Bir", - "Lie","Rugp","Rugs","Spa","Lap","Gru" ], + monthNames: [ "Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", + "Liepa", "Rugpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis" ], + monthNamesShort: [ "Sau", "Vas", "Kov", "Bal", "Geg", "Bir", + "Lie", "Rugp", "Rugs", "Spa", "Lap", "Gru" ], dayNames: [ "sekmadienis", "pirmadienis", @@ -30,8 +33,8 @@ datepicker.regional.lt = { "penktadienis", "šeštadienis" ], - dayNamesShort: [ "sek","pir","ant","tre","ket","pen","šeš" ], - dayNamesMin: [ "Se","Pr","An","Tr","Ke","Pe","Še" ], + dayNamesShort: [ "sek", "pir", "ant", "tre", "ket", "pen", "šeš" ], + dayNamesMin: [ "Se", "Pr", "An", "Tr", "Ke", "Pe", "Še" ], weekHeader: "SAV", dateFormat: "yy-mm-dd", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.lt ); return datepicker.regional.lt; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-lv.js b/lib/web/jquery/ui-modules/i18n/datepicker-lv.js index 04556fbcf7003..25d4a37410253 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-lv.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-lv.js @@ -1,6 +1,8 @@ /* Latvian (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* @author Arturas Paleicikas <arturas.paleicikas@metasite.net> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.lv = { closeText: "Aizvērt", prevText: "Iepr.", nextText: "Nāk.", currentText: "Šodien", - monthNames: [ "Janvāris","Februāris","Marts","Aprīlis","Maijs","Jūnijs", - "Jūlijs","Augusts","Septembris","Oktobris","Novembris","Decembris" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Mai","Jūn", - "Jūl","Aug","Sep","Okt","Nov","Dec" ], + monthNames: [ "Janvāris", "Februāris", "Marts", "Aprīlis", "Maijs", "Jūnijs", + "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Mai", "Jūn", + "Jūl", "Aug", "Sep", "Okt", "Nov", "Dec" ], dayNames: [ "svētdiena", "pirmdiena", @@ -30,8 +33,8 @@ datepicker.regional.lv = { "piektdiena", "sestdiena" ], - dayNamesShort: [ "svt","prm","otr","tre","ctr","pkt","sst" ], - dayNamesMin: [ "Sv","Pr","Ot","Tr","Ct","Pk","Ss" ], + dayNamesShort: [ "svt", "prm", "otr", "tre", "ctr", "pkt", "sst" ], + dayNamesMin: [ "Sv", "Pr", "Ot", "Tr", "Ct", "Pk", "Ss" ], weekHeader: "Ned.", dateFormat: "dd.mm.yy", firstDay: 1, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional.lv ); return datepicker.regional.lv; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-mk.js b/lib/web/jquery/ui-modules/i18n/datepicker-mk.js index 97864ab15165f..f7999baec8c9a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-mk.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-mk.js @@ -1,6 +1,8 @@ /* Macedonian i18n for the jQuery UI date picker plugin. */ /* Written by Stojce Slavkovski. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.mk = { closeText: "Затвори", prevText: "<", nextText: ">", currentText: "Денес", - monthNames: [ "Јануари","Февруари","Март","Април","Мај","Јуни", - "Јули","Август","Септември","Октомври","Ноември","Декември" ], - monthNamesShort: [ "Јан","Фев","Мар","Апр","Мај","Јун", - "Јул","Авг","Сеп","Окт","Ное","Дек" ], - dayNames: [ "Недела","Понеделник","Вторник","Среда","Четврток","Петок","Сабота" ], - dayNamesShort: [ "Нед","Пон","Вто","Сре","Чет","Пет","Саб" ], - dayNamesMin: [ "Не","По","Вт","Ср","Че","Пе","Са" ], + monthNames: [ "Јануари", "Февруари", "Март", "Април", "Мај", "Јуни", + "Јули", "Август", "Септември", "Октомври", "Ноември", "Декември" ], + monthNamesShort: [ "Јан", "Фев", "Мар", "Апр", "Мај", "Јун", + "Јул", "Авг", "Сеп", "Окт", "Ное", "Дек" ], + dayNames: [ "Недела", "Понеделник", "Вторник", "Среда", "Четврток", "Петок", "Сабота" ], + dayNamesShort: [ "Нед", "Пон", "Вто", "Сре", "Чет", "Пет", "Саб" ], + dayNamesMin: [ "Не", "По", "Вт", "Ср", "Че", "Пе", "Са" ], weekHeader: "Сед", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.mk ); return datepicker.regional.mk; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ml.js b/lib/web/jquery/ui-modules/i18n/datepicker-ml.js index 440e09e0346c2..b6223c8e89823 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ml.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ml.js @@ -1,6 +1,8 @@ /* Malayalam (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Saji Nediyanchath (saji89@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ml = { closeText: "ശരി", prevText: "മുന്നത്തെ", nextText: "അടുത്തത് ", currentText: "ഇന്ന്", - monthNames: [ "ജനുവരി","ഫെബ്രുവരി","മാര്‍ച്ച്","ഏപ്രില്‍","മേയ്","ജൂണ്‍", - "ജൂലൈ","ആഗസ്റ്റ്","സെപ്റ്റംബര്‍","ഒക്ടോബര്‍","നവംബര്‍","ഡിസംബര്‍" ], + monthNames: [ "ജനുവരി", "ഫെബ്രുവരി", "മാര്‍ച്ച്", "ഏപ്രില്‍", "മേയ്", "ജൂണ്‍", + "ജൂലൈ", "ആഗസ്റ്റ്", "സെപ്റ്റംബര്‍", "ഒക്ടോബര്‍", "നവംബര്‍", "ഡിസംബര്‍" ], monthNamesShort: [ "ജനു", "ഫെബ്", "മാര്‍", "ഏപ്രി", "മേയ്", "ജൂണ്‍", "ജൂലാ", "ആഗ", "സെപ്", "ഒക്ടോ", "നവം", "ഡിസ" ], dayNames: [ "ഞായര്‍", "തിങ്കള്‍", "ചൊവ്വ", "ബുധന്‍", "വ്യാഴം", "വെള്ളി", "ശനി" ], dayNamesShort: [ "ഞായ", "തിങ്ക", "ചൊവ്വ", "ബുധ", "വ്യാഴം", "വെള്ളി", "ശനി" ], - dayNamesMin: [ "ഞാ","തി","ചൊ","ബു","വ്യാ","വെ","ശ" ], + dayNamesMin: [ "ഞാ", "തി", "ചൊ", "ബു", "വ്യാ", "വെ", "ശ" ], weekHeader: "ആ", dateFormat: "dd/mm/yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ml ); return datepicker.regional.ml; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ms.js b/lib/web/jquery/ui-modules/i18n/datepicker-ms.js index 58bc4f579e0af..344b7683e969a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ms.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ms.js @@ -1,6 +1,8 @@ /* Malaysian initialisation for the jQuery UI date picker plugin. */ /* Written by Mohd Nawawi Mohamad Jamili (nawawi@ronggeng.net). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ms = { closeText: "Tutup", prevText: "<Sebelum", nextText: "Selepas>", currentText: "hari ini", - monthNames: [ "Januari","Februari","Mac","April","Mei","Jun", - "Julai","Ogos","September","Oktober","November","Disember" ], - monthNamesShort: [ "Jan","Feb","Mac","Apr","Mei","Jun", - "Jul","Ogo","Sep","Okt","Nov","Dis" ], - dayNames: [ "Ahad","Isnin","Selasa","Rabu","Khamis","Jumaat","Sabtu" ], - dayNamesShort: [ "Aha","Isn","Sel","Rab","kha","Jum","Sab" ], - dayNamesMin: [ "Ah","Is","Se","Ra","Kh","Ju","Sa" ], + monthNames: [ "Januari", "Februari", "Mac", "April", "Mei", "Jun", + "Julai", "Ogos", "September", "Oktober", "November", "Disember" ], + monthNamesShort: [ "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", + "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis" ], + dayNames: [ "Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu" ], + dayNamesShort: [ "Aha", "Isn", "Sel", "Rab", "kha", "Jum", "Sab" ], + dayNamesMin: [ "Ah", "Is", "Se", "Ra", "Kh", "Ju", "Sa" ], weekHeader: "Mg", dateFormat: "dd/mm/yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ms ); return datepicker.regional.ms; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-nb.js b/lib/web/jquery/ui-modules/i18n/datepicker-nb.js index eb1112bc50acf..b4fd237b91d88 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-nb.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-nb.js @@ -1,6 +1,8 @@ /* Norwegian Bokmål initialisation for the jQuery UI date picker plugin. */ /* Written by Bjørn Johansen (post@bjornjohansen.no). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.nb = { closeText: "Lukk", @@ -31,10 +34,10 @@ datepicker.regional.nb = { "november", "desember" ], - monthNamesShort: [ "jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des" ], - dayNamesShort: [ "søn","man","tir","ons","tor","fre","lør" ], - dayNames: [ "søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag" ], - dayNamesMin: [ "sø","ma","ti","on","to","fr","lø" ], + monthNamesShort: [ "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des" ], + dayNamesShort: [ "søn", "man", "tir", "ons", "tor", "fre", "lør" ], + dayNames: [ "søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag" ], + dayNamesMin: [ "sø", "ma", "ti", "on", "to", "fr", "lø" ], weekHeader: "Uke", dateFormat: "dd.mm.yy", firstDay: 1, @@ -46,4 +49,4 @@ datepicker.setDefaults( datepicker.regional.nb ); return datepicker.regional.nb; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-nl-BE.js b/lib/web/jquery/ui-modules/i18n/datepicker-nl-BE.js index 9ea22002d684c..ae574739e7f5e 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-nl-BE.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-nl-BE.js @@ -1,6 +1,8 @@ /* Dutch (Belgium) initialisation for the jQuery UI date picker plugin. */ /* David De Sloovere @DavidDeSloovere */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "nl-BE" ] = { closeText: "Sluiten", @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "nl-BE" ] ); return datepicker.regional[ "nl-BE" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-nl.js b/lib/web/jquery/ui-modules/i18n/datepicker-nl.js index 7fcbff1ac1088..19df79ca7141b 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-nl.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-nl.js @@ -1,6 +1,8 @@ /* Dutch (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Mathias Bynens <http://mathiasbynens.be/> */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.nl = { closeText: "Sluiten", @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.nl ); return datepicker.regional.nl; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-nn.js b/lib/web/jquery/ui-modules/i18n/datepicker-nn.js index bacd481a01b11..967351c95bc3b 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-nn.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-nn.js @@ -1,6 +1,8 @@ /* Norwegian Nynorsk initialisation for the jQuery UI date picker plugin. */ /* Written by Bjørn Johansen (post@bjornjohansen.no). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.nn = { closeText: "Lukk", @@ -31,10 +34,10 @@ datepicker.regional.nn = { "november", "desember" ], - monthNamesShort: [ "jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des" ], - dayNamesShort: [ "sun","mån","tys","ons","tor","fre","lau" ], - dayNames: [ "sundag","måndag","tysdag","onsdag","torsdag","fredag","laurdag" ], - dayNamesMin: [ "su","må","ty","on","to","fr","la" ], + monthNamesShort: [ "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des" ], + dayNamesShort: [ "sun", "mån", "tys", "ons", "tor", "fre", "lau" ], + dayNames: [ "sundag", "måndag", "tysdag", "onsdag", "torsdag", "fredag", "laurdag" ], + dayNamesMin: [ "su", "må", "ty", "on", "to", "fr", "la" ], weekHeader: "Veke", dateFormat: "dd.mm.yy", firstDay: 1, @@ -46,4 +49,4 @@ datepicker.setDefaults( datepicker.regional.nn ); return datepicker.regional.nn; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-no.js b/lib/web/jquery/ui-modules/i18n/datepicker-no.js index 8a755aae9192b..4b1b54fa276fe 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-no.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-no.js @@ -2,6 +2,8 @@ /* Written by Naimdjon Takhirov (naimdjon@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -11,7 +13,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.no = { closeText: "Lukk", @@ -32,10 +35,10 @@ datepicker.regional.no = { "november", "desember" ], - monthNamesShort: [ "jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des" ], - dayNamesShort: [ "søn","man","tir","ons","tor","fre","lør" ], - dayNames: [ "søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag" ], - dayNamesMin: [ "sø","ma","ti","on","to","fr","lø" ], + monthNamesShort: [ "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des" ], + dayNamesShort: [ "søn", "man", "tir", "ons", "tor", "fre", "lør" ], + dayNames: [ "søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag" ], + dayNamesMin: [ "sø", "ma", "ti", "on", "to", "fr", "lø" ], weekHeader: "Uke", dateFormat: "dd.mm.yy", firstDay: 1, @@ -47,4 +50,4 @@ datepicker.setDefaults( datepicker.regional.no ); return datepicker.regional.no; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-pl.js b/lib/web/jquery/ui-modules/i18n/datepicker-pl.js index c2fddc13278c8..60f9fbb2d4b6a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-pl.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-pl.js @@ -1,6 +1,8 @@ /* Polish initialisation for the jQuery UI date picker plugin. */ /* Written by Jacek Wysocki (jacek.wysocki@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.pl = { closeText: "Zamknij", prevText: "<Poprzedni", nextText: "Następny>", currentText: "Dziś", - monthNames: [ "Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec", - "Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień" ], - monthNamesShort: [ "Sty","Lu","Mar","Kw","Maj","Cze", - "Lip","Sie","Wrz","Pa","Lis","Gru" ], - dayNames: [ "Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota" ], - dayNamesShort: [ "Nie","Pn","Wt","Śr","Czw","Pt","So" ], - dayNamesMin: [ "N","Pn","Wt","Śr","Cz","Pt","So" ], + monthNames: [ "Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", + "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień" ], + monthNamesShort: [ "Sty", "Lu", "Mar", "Kw", "Maj", "Cze", + "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru" ], + dayNames: [ "Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota" ], + dayNamesShort: [ "Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So" ], + dayNamesMin: [ "N", "Pn", "Wt", "Śr", "Cz", "Pt", "So" ], weekHeader: "Tydz", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.pl ); return datepicker.regional.pl; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-pt-BR.js b/lib/web/jquery/ui-modules/i18n/datepicker-pt-BR.js index aeae7ca4ec3d9..1c84bf34daf05 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-pt-BR.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-pt-BR.js @@ -1,6 +1,8 @@ /* Brazilian initialisation for the jQuery UI date picker plugin. */ /* Written by Leonildo Costa Silva (leocsilva@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "pt-BR" ] = { closeText: "Fechar", prevText: "<Anterior", nextText: "Próximo>", currentText: "Hoje", - monthNames: [ "Janeiro","Fevereiro","Março","Abril","Maio","Junho", - "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro" ], - monthNamesShort: [ "Jan","Fev","Mar","Abr","Mai","Jun", - "Jul","Ago","Set","Out","Nov","Dez" ], + monthNames: [ "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", + "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" ], + monthNamesShort: [ "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", + "Jul", "Ago", "Set", "Out", "Nov", "Dez" ], dayNames: [ "Domingo", "Segunda-feira", @@ -30,8 +33,8 @@ datepicker.regional[ "pt-BR" ] = { "Sexta-feira", "Sábado" ], - dayNamesShort: [ "Dom","Seg","Ter","Qua","Qui","Sex","Sáb" ], - dayNamesMin: [ "Dom","Seg","Ter","Qua","Qui","Sex","Sáb" ], + dayNamesShort: [ "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb" ], + dayNamesMin: [ "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb" ], weekHeader: "Sm", dateFormat: "dd/mm/yy", firstDay: 0, @@ -42,4 +45,4 @@ datepicker.setDefaults( datepicker.regional[ "pt-BR" ] ); return datepicker.regional[ "pt-BR" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-pt.js b/lib/web/jquery/ui-modules/i18n/datepicker-pt.js index b1afd7b3d05bc..3112cb4fc4fd6 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-pt.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-pt.js @@ -1,5 +1,7 @@ /* Portuguese initialisation for the jQuery UI date picker plugin. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -9,17 +11,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.pt = { closeText: "Fechar", prevText: "Anterior", nextText: "Seguinte", currentText: "Hoje", - monthNames: [ "Janeiro","Fevereiro","Março","Abril","Maio","Junho", - "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro" ], - monthNamesShort: [ "Jan","Fev","Mar","Abr","Mai","Jun", - "Jul","Ago","Set","Out","Nov","Dez" ], + monthNames: [ "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", + "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" ], + monthNamesShort: [ "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", + "Jul", "Ago", "Set", "Out", "Nov", "Dez" ], dayNames: [ "Domingo", "Segunda-feira", @@ -29,11 +32,11 @@ datepicker.regional.pt = { "Sexta-feira", "Sábado" ], - dayNamesShort: [ "Dom","Seg","Ter","Qua","Qui","Sex","Sáb" ], - dayNamesMin: [ "Dom","Seg","Ter","Qua","Qui","Sex","Sáb" ], + dayNamesShort: [ "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb" ], + dayNamesMin: [ "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb" ], weekHeader: "Sem", dateFormat: "dd/mm/yy", - firstDay: 0, + firstDay: 1, isRTL: false, showMonthAfterYear: false, yearSuffix: "" }; @@ -41,4 +44,4 @@ datepicker.setDefaults( datepicker.regional.pt ); return datepicker.regional.pt; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-rm.js b/lib/web/jquery/ui-modules/i18n/datepicker-rm.js index 89a5c77a118e1..439e8c8e1138a 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-rm.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-rm.js @@ -1,6 +1,8 @@ /* Romansh initialisation for the jQuery UI date picker plugin. */ /* Written by Yvonne Gienal (yvonne.gienal@educa.ch). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.rm = { closeText: "Serrar", @@ -45,9 +48,9 @@ datepicker.regional.rm = { "Nov", "Dec" ], - dayNames: [ "Dumengia","Glindesdi","Mardi","Mesemna","Gievgia","Venderdi","Sonda" ], - dayNamesShort: [ "Dum","Gli","Mar","Mes","Gie","Ven","Som" ], - dayNamesMin: [ "Du","Gl","Ma","Me","Gi","Ve","So" ], + dayNames: [ "Dumengia", "Glindesdi", "Mardi", "Mesemna", "Gievgia", "Venderdi", "Sonda" ], + dayNamesShort: [ "Dum", "Gli", "Mar", "Mes", "Gie", "Ven", "Som" ], + dayNamesMin: [ "Du", "Gl", "Ma", "Me", "Gi", "Ve", "So" ], weekHeader: "emna", dateFormat: "dd/mm/yy", firstDay: 1, @@ -58,4 +61,4 @@ datepicker.setDefaults( datepicker.regional.rm ); return datepicker.regional.rm; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ro.js b/lib/web/jquery/ui-modules/i18n/datepicker-ro.js index b26665c2564be..f03ee15c45a9b 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ro.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ro.js @@ -4,6 +4,8 @@ * and Ionut G. Stan (ionut.g.stan@gmail.com) */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -13,20 +15,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ro = { closeText: "Închide", prevText: "« Luna precedentă", nextText: "Luna următoare »", currentText: "Azi", - monthNames: [ "Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie", - "Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie" ], + monthNames: [ "Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", + "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie" ], monthNamesShort: [ "Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec" ], dayNames: [ "Duminică", "Luni", "Marţi", "Miercuri", "Joi", "Vineri", "Sâmbătă" ], dayNamesShort: [ "Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm" ], - dayNamesMin: [ "Du","Lu","Ma","Mi","Jo","Vi","Sâ" ], + dayNamesMin: [ "Du", "Lu", "Ma", "Mi", "Jo", "Vi", "Sâ" ], weekHeader: "Săpt", dateFormat: "dd.mm.yy", firstDay: 1, @@ -37,4 +40,4 @@ datepicker.setDefaults( datepicker.regional.ro ); return datepicker.regional.ro; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ru.js b/lib/web/jquery/ui-modules/i18n/datepicker-ru.js index 223e77645e7f5..61d1151095ffd 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ru.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ru.js @@ -1,6 +1,8 @@ /* Russian (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Andrew Stromnov (stromnov@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ru = { closeText: "Закрыть", prevText: "<Пред", nextText: "След>", currentText: "Сегодня", - monthNames: [ "Январь","Февраль","Март","Апрель","Май","Июнь", - "Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь" ], - monthNamesShort: [ "Янв","Фев","Мар","Апр","Май","Июн", - "Июл","Авг","Сен","Окт","Ноя","Дек" ], - dayNames: [ "воскресенье","понедельник","вторник","среда","четверг","пятница","суббота" ], - dayNamesShort: [ "вск","пнд","втр","срд","чтв","птн","сбт" ], - dayNamesMin: [ "Вс","Пн","Вт","Ср","Чт","Пт","Сб" ], + monthNames: [ "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", + "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" ], + monthNamesShort: [ "Янв", "Фев", "Мар", "Апр", "Май", "Июн", + "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" ], + dayNames: [ "воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота" ], + dayNamesShort: [ "вск", "пнд", "втр", "срд", "чтв", "птн", "сбт" ], + dayNamesMin: [ "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб" ], weekHeader: "Нед", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.ru ); return datepicker.regional.ru; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sk.js b/lib/web/jquery/ui-modules/i18n/datepicker-sk.js index 16d8bdfe4e4d1..c28750c248e95 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sk.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sk.js @@ -1,6 +1,8 @@ /* Slovak initialisation for the jQuery UI date picker plugin. */ /* Written by Vojtech Rinik (vojto@hmm.sk). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.sk = { closeText: "Zavrieť", prevText: "<Predchádzajúci", nextText: "Nasledujúci>", currentText: "Dnes", - monthNames: [ "január","február","marec","apríl","máj","jún", - "júl","august","september","október","november","december" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Máj","Jún", - "Júl","Aug","Sep","Okt","Nov","Dec" ], - dayNames: [ "nedeľa","pondelok","utorok","streda","štvrtok","piatok","sobota" ], - dayNamesShort: [ "Ned","Pon","Uto","Str","Štv","Pia","Sob" ], - dayNamesMin: [ "Ne","Po","Ut","St","Št","Pia","So" ], + monthNames: [ "január", "február", "marec", "apríl", "máj", "jún", + "júl", "august", "september", "október", "november", "december" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Máj", "Jún", + "Júl", "Aug", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "nedeľa", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota" ], + dayNamesShort: [ "Ned", "Pon", "Uto", "Str", "Štv", "Pia", "Sob" ], + dayNamesMin: [ "Ne", "Po", "Ut", "St", "Št", "Pia", "So" ], weekHeader: "Ty", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.sk ); return datepicker.regional.sk; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sl.js b/lib/web/jquery/ui-modules/i18n/datepicker-sl.js index 6891624926a12..15aa9e4ccf23c 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sl.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sl.js @@ -2,6 +2,8 @@ /* Written by Jaka Jancar (jaka@kubje.org). */ /* c = č, s = š z = ž C = Č S = Š Z = Ž */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -11,20 +13,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.sl = { closeText: "Zapri", prevText: "<Prejšnji", nextText: "Naslednji>", currentText: "Trenutni", - monthNames: [ "Januar","Februar","Marec","April","Maj","Junij", - "Julij","Avgust","September","Oktober","November","December" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Avg","Sep","Okt","Nov","Dec" ], - dayNames: [ "Nedelja","Ponedeljek","Torek","Sreda","Četrtek","Petek","Sobota" ], - dayNamesShort: [ "Ned","Pon","Tor","Sre","Čet","Pet","Sob" ], - dayNamesMin: [ "Ne","Po","To","Sr","Če","Pe","So" ], + monthNames: [ "Januar", "Februar", "Marec", "April", "Maj", "Junij", + "Julij", "Avgust", "September", "Oktober", "November", "December" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", + "Jul", "Avg", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "Nedelja", "Ponedeljek", "Torek", "Sreda", "Četrtek", "Petek", "Sobota" ], + dayNamesShort: [ "Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob" ], + dayNamesMin: [ "Ne", "Po", "To", "Sr", "Če", "Pe", "So" ], weekHeader: "Teden", dateFormat: "dd.mm.yy", firstDay: 1, @@ -35,4 +38,4 @@ datepicker.setDefaults( datepicker.regional.sl ); return datepicker.regional.sl; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sq.js b/lib/web/jquery/ui-modules/i18n/datepicker-sq.js index 34fe66a59cdd4..470a0301b483c 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sq.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sq.js @@ -1,6 +1,8 @@ /* Albanian initialisation for the jQuery UI date picker plugin. */ /* Written by Flakron Bytyqi (flakron@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.sq = { closeText: "mbylle", prevText: "<mbrapa", nextText: "Përpara>", currentText: "sot", - monthNames: [ "Janar","Shkurt","Mars","Prill","Maj","Qershor", - "Korrik","Gusht","Shtator","Tetor","Nëntor","Dhjetor" ], - monthNamesShort: [ "Jan","Shk","Mar","Pri","Maj","Qer", - "Kor","Gus","Sht","Tet","Nën","Dhj" ], - dayNames: [ "E Diel","E Hënë","E Martë","E Mërkurë","E Enjte","E Premte","E Shtune" ], - dayNamesShort: [ "Di","Hë","Ma","Më","En","Pr","Sh" ], - dayNamesMin: [ "Di","Hë","Ma","Më","En","Pr","Sh" ], + monthNames: [ "Janar", "Shkurt", "Mars", "Prill", "Maj", "Qershor", + "Korrik", "Gusht", "Shtator", "Tetor", "Nëntor", "Dhjetor" ], + monthNamesShort: [ "Jan", "Shk", "Mar", "Pri", "Maj", "Qer", + "Kor", "Gus", "Sht", "Tet", "Nën", "Dhj" ], + dayNames: [ "E Diel", "E Hënë", "E Martë", "E Mërkurë", "E Enjte", "E Premte", "E Shtune" ], + dayNamesShort: [ "Di", "Hë", "Ma", "Më", "En", "Pr", "Sh" ], + dayNamesMin: [ "Di", "Hë", "Ma", "Më", "En", "Pr", "Sh" ], weekHeader: "Ja", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.sq ); return datepicker.regional.sq; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sr-SR.js b/lib/web/jquery/ui-modules/i18n/datepicker-sr-SR.js index e9db26a42e2db..2b75c408e9164 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sr-SR.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sr-SR.js @@ -1,6 +1,8 @@ /* Serbian i18n for the jQuery UI date picker plugin. */ /* Written by Dejan Dimić. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "sr-SR" ] = { closeText: "Zatvori", prevText: "<", nextText: ">", currentText: "Danas", - monthNames: [ "Januar","Februar","Mart","April","Maj","Jun", - "Jul","Avgust","Septembar","Oktobar","Novembar","Decembar" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Avg","Sep","Okt","Nov","Dec" ], - dayNames: [ "Nedelja","Ponedeljak","Utorak","Sreda","Četvrtak","Petak","Subota" ], - dayNamesShort: [ "Ned","Pon","Uto","Sre","Čet","Pet","Sub" ], - dayNamesMin: [ "Ne","Po","Ut","Sr","Če","Pe","Su" ], + monthNames: [ "Januar", "Februar", "Mart", "April", "Maj", "Jun", + "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar" ], + monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "Maj", "Jun", + "Jul", "Avg", "Sep", "Okt", "Nov", "Dec" ], + dayNames: [ "Nedelja", "Ponedeljak", "Utorak", "Sreda", "Četvrtak", "Petak", "Subota" ], + dayNamesShort: [ "Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub" ], + dayNamesMin: [ "Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su" ], weekHeader: "Sed", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "sr-SR" ] ); return datepicker.regional[ "sr-SR" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sr.js b/lib/web/jquery/ui-modules/i18n/datepicker-sr.js index fa8827aa128b9..c64069ab3a7b0 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sr.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sr.js @@ -1,6 +1,8 @@ /* Serbian i18n for the jQuery UI date picker plugin. */ /* Written by Dejan Dimić. */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.sr = { closeText: "Затвори", prevText: "<", nextText: ">", currentText: "Данас", - monthNames: [ "Јануар","Фебруар","Март","Април","Мај","Јун", - "Јул","Август","Септембар","Октобар","Новембар","Децембар" ], - monthNamesShort: [ "Јан","Феб","Мар","Апр","Мај","Јун", - "Јул","Авг","Сеп","Окт","Нов","Дец" ], - dayNames: [ "Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота" ], - dayNamesShort: [ "Нед","Пон","Уто","Сре","Чет","Пет","Суб" ], - dayNamesMin: [ "Не","По","Ут","Ср","Че","Пе","Су" ], + monthNames: [ "Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", + "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар" ], + monthNamesShort: [ "Јан", "Феб", "Мар", "Апр", "Мај", "Јун", + "Јул", "Авг", "Сеп", "Окт", "Нов", "Дец" ], + dayNames: [ "Недеља", "Понедељак", "Уторак", "Среда", "Четвртак", "Петак", "Субота" ], + dayNamesShort: [ "Нед", "Пон", "Уто", "Сре", "Чет", "Пет", "Суб" ], + dayNamesMin: [ "Не", "По", "Ут", "Ср", "Че", "Пе", "Су" ], weekHeader: "Сед", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.sr ); return datepicker.regional.sr; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-sv.js b/lib/web/jquery/ui-modules/i18n/datepicker-sv.js index 92686efa4f768..6fe51e3e74de5 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-sv.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-sv.js @@ -1,6 +1,8 @@ /* Swedish initialisation for the jQuery UI date picker plugin. */ /* Written by Anders Ekdahl ( anders@nomadiz.se). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.sv = { closeText: "Stäng", prevText: "«Förra", nextText: "Nästa»", currentText: "Idag", - monthNames: [ "Januari","Februari","Mars","April","Maj","Juni", - "Juli","Augusti","September","Oktober","November","December" ], - monthNamesShort: [ "Jan","Feb","Mar","Apr","Maj","Jun", - "Jul","Aug","Sep","Okt","Nov","Dec" ], - dayNamesShort: [ "Sön","Mån","Tis","Ons","Tor","Fre","Lör" ], - dayNames: [ "Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag" ], - dayNamesMin: [ "Sö","Må","Ti","On","To","Fr","Lö" ], + monthNames: [ "januari", "februari", "mars", "april", "maj", "juni", + "juli", "augusti", "september", "oktober", "november", "december" ], + monthNamesShort: [ "jan.", "feb.", "mars", "apr.", "maj", "juni", + "juli", "aug.", "sep.", "okt.", "nov.", "dec." ], + dayNamesShort: [ "sön", "mån", "tis", "ons", "tor", "fre", "lör" ], + dayNames: [ "söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag" ], + dayNamesMin: [ "sö", "må", "ti", "on", "to", "fr", "lö" ], weekHeader: "Ve", dateFormat: "yy-mm-dd", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.sv ); return datepicker.regional.sv; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-ta.js b/lib/web/jquery/ui-modules/i18n/datepicker-ta.js index 722614dd06ca8..730c5df9035be 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-ta.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-ta.js @@ -1,6 +1,8 @@ /* Tamil (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by S A Sureshkumar (saskumar@live.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,17 +12,18 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.ta = { closeText: "மூடு", prevText: "முன்னையது", nextText: "அடுத்தது", currentText: "இன்று", - monthNames: [ "தை","மாசி","பங்குனி","சித்திரை","வைகாசி","ஆனி", - "ஆடி","ஆவணி","புரட்டாசி","ஐப்பசி","கார்த்திகை","மார்கழி" ], - monthNamesShort: [ "தை","மாசி","பங்","சித்","வைகா","ஆனி", - "ஆடி","ஆவ","புர","ஐப்","கார்","மார்" ], + monthNames: [ "தை", "மாசி", "பங்குனி", "சித்திரை", "வைகாசி", "ஆனி", + "ஆடி", "ஆவணி", "புரட்டாசி", "ஐப்பசி", "கார்த்திகை", "மார்கழி" ], + monthNamesShort: [ "தை", "மாசி", "பங்", "சித்", "வைகா", "ஆனி", + "ஆடி", "ஆவ", "புர", "ஐப்", "கார்", "மார்" ], dayNames: [ "ஞாயிற்றுக்கிழமை", "திங்கட்கிழமை", @@ -39,7 +42,7 @@ datepicker.regional.ta = { "வெள்ளி", "சனி" ], - dayNamesMin: [ "ஞா","தி","செ","பு","வி","வெ","ச" ], + dayNamesMin: [ "ஞா", "தி", "செ", "பு", "வி", "வெ", "ச" ], weekHeader: "Не", dateFormat: "dd/mm/yy", firstDay: 1, @@ -50,4 +53,4 @@ datepicker.setDefaults( datepicker.regional.ta ); return datepicker.regional.ta; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-th.js b/lib/web/jquery/ui-modules/i18n/datepicker-th.js index 6de48cf968156..10c2833214eb9 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-th.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-th.js @@ -1,6 +1,8 @@ /* Thai initialisation for the jQuery UI date picker plugin. */ /* Written by pipo (pipo@sixhead.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.th = { closeText: "ปิด", prevText: "« ย้อน", nextText: "ถัดไป »", currentText: "วันนี้", - monthNames: [ "มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน", - "กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม" ], - monthNamesShort: [ "ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.", - "ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค." ], - dayNames: [ "อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์" ], - dayNamesShort: [ "อา.","จ.","อ.","พ.","พฤ.","ศ.","ส." ], - dayNamesMin: [ "อา.","จ.","อ.","พ.","พฤ.","ศ.","ส." ], + monthNames: [ "มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", + "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม" ], + monthNamesShort: [ "ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", + "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค." ], + dayNames: [ "อาทิตย์", "จันทร์", "อังคาร", "พุธ", "พฤหัสบดี", "ศุกร์", "เสาร์" ], + dayNamesShort: [ "อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส." ], + dayNamesMin: [ "อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส." ], weekHeader: "Wk", dateFormat: "dd/mm/yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.th ); return datepicker.regional.th; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-tj.js b/lib/web/jquery/ui-modules/i18n/datepicker-tj.js index 8ede4ddcb03f8..bdc71f17a6cd2 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-tj.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-tj.js @@ -1,6 +1,8 @@ /* Tajiki (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* Written by Abdurahmon Saidov (saidovab@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.tj = { closeText: "Идома", prevText: "<Қафо", nextText: "Пеш>", currentText: "Имрӯз", - monthNames: [ "Январ","Феврал","Март","Апрел","Май","Июн", - "Июл","Август","Сентябр","Октябр","Ноябр","Декабр" ], - monthNamesShort: [ "Янв","Фев","Мар","Апр","Май","Июн", - "Июл","Авг","Сен","Окт","Ноя","Дек" ], - dayNames: [ "якшанбе","душанбе","сешанбе","чоршанбе","панҷшанбе","ҷумъа","шанбе" ], - dayNamesShort: [ "якш","душ","сеш","чор","пан","ҷум","шан" ], - dayNamesMin: [ "Як","Дш","Сш","Чш","Пш","Ҷм","Шн" ], + monthNames: [ "Январ", "Феврал", "Март", "Апрел", "Май", "Июн", + "Июл", "Август", "Сентябр", "Октябр", "Ноябр", "Декабр" ], + monthNamesShort: [ "Янв", "Фев", "Мар", "Апр", "Май", "Июн", + "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" ], + dayNames: [ "якшанбе", "душанбе", "сешанбе", "чоршанбе", "панҷшанбе", "ҷумъа", "шанбе" ], + dayNamesShort: [ "якш", "душ", "сеш", "чор", "пан", "ҷум", "шан" ], + dayNamesMin: [ "Як", "Дш", "Сш", "Чш", "Пш", "Ҷм", "Шн" ], weekHeader: "Хф", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.tj ); return datepicker.regional.tj; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-tr.js b/lib/web/jquery/ui-modules/i18n/datepicker-tr.js index 8328e2199260f..220d66e64e100 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-tr.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-tr.js @@ -1,6 +1,8 @@ /* Turkish initialisation for the jQuery UI date picker plugin. */ /* Written by Izzet Emre Erkan (kara@karalamalar.net). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.tr = { closeText: "kapat", prevText: "<geri", nextText: "ileri>", currentText: "bugün", - monthNames: [ "Ocak","Şubat","Mart","Nisan","Mayıs","Haziran", - "Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık" ], - monthNamesShort: [ "Oca","Şub","Mar","Nis","May","Haz", - "Tem","Ağu","Eyl","Eki","Kas","Ara" ], - dayNames: [ "Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi" ], - dayNamesShort: [ "Pz","Pt","Sa","Ça","Pe","Cu","Ct" ], - dayNamesMin: [ "Pz","Pt","Sa","Ça","Pe","Cu","Ct" ], + monthNames: [ "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", + "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık" ], + monthNamesShort: [ "Oca", "Şub", "Mar", "Nis", "May", "Haz", + "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara" ], + dayNames: [ "Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi" ], + dayNamesShort: [ "Pz", "Pt", "Sa", "Ça", "Pe", "Cu", "Ct" ], + dayNamesMin: [ "Pz", "Pt", "Sa", "Ça", "Pe", "Cu", "Ct" ], weekHeader: "Hf", dateFormat: "dd.mm.yy", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.tr ); return datepicker.regional.tr; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-uk.js b/lib/web/jquery/ui-modules/i18n/datepicker-uk.js index c82501ad28d7f..083462803aa22 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-uk.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-uk.js @@ -2,6 +2,8 @@ /* Written by Maxim Drogobitskiy (maxdao@gmail.com). */ /* Corrected by Igor Milla (igor.fsp.milla@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -11,20 +13,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.uk = { closeText: "Закрити", prevText: "<", nextText: ">", currentText: "Сьогодні", - monthNames: [ "Січень","Лютий","Березень","Квітень","Травень","Червень", - "Липень","Серпень","Вересень","Жовтень","Листопад","Грудень" ], - monthNamesShort: [ "Січ","Лют","Бер","Кві","Тра","Чер", - "Лип","Сер","Вер","Жов","Лис","Гру" ], - dayNames: [ "неділя","понеділок","вівторок","середа","четвер","п’ятниця","субота" ], - dayNamesShort: [ "нед","пнд","вів","срд","чтв","птн","сбт" ], - dayNamesMin: [ "Нд","Пн","Вт","Ср","Чт","Пт","Сб" ], + monthNames: [ "Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", + "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень" ], + monthNamesShort: [ "Січ", "Лют", "Бер", "Кві", "Тра", "Чер", + "Лип", "Сер", "Вер", "Жов", "Лис", "Гру" ], + dayNames: [ "неділя", "понеділок", "вівторок", "середа", "четвер", "п’ятниця", "субота" ], + dayNamesShort: [ "нед", "пнд", "вів", "срд", "чтв", "птн", "сбт" ], + dayNamesMin: [ "Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб" ], weekHeader: "Тиж", dateFormat: "dd.mm.yy", firstDay: 1, @@ -35,4 +38,4 @@ datepicker.setDefaults( datepicker.regional.uk ); return datepicker.regional.uk; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-vi.js b/lib/web/jquery/ui-modules/i18n/datepicker-vi.js index 2c208ab71c379..e06b702c12db0 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-vi.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-vi.js @@ -1,6 +1,8 @@ /* Vietnamese initialisation for the jQuery UI date picker plugin. */ /* Translated by Le Thanh Huy (lthanhhuy@cit.ctu.edu.vn). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,7 +12,8 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional.vi = { closeText: "Đóng", @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional.vi ); return datepicker.regional.vi; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-zh-CN.js b/lib/web/jquery/ui-modules/i18n/datepicker-zh-CN.js index 91f99b4ed5166..84092904b6929 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-zh-CN.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-zh-CN.js @@ -1,6 +1,8 @@ /* Chinese initialisation for the jQuery UI date picker plugin. */ /* Written by Cloudream (cloudream@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "zh-CN" ] = { closeText: "关闭", prevText: "<上月", nextText: "下月>", currentText: "今天", - monthNames: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - monthNamesShort: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - dayNames: [ "星期日","星期一","星期二","星期三","星期四","星期五","星期六" ], - dayNamesShort: [ "周日","周一","周二","周三","周四","周五","周六" ], - dayNamesMin: [ "日","一","二","三","四","五","六" ], + monthNames: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + monthNamesShort: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + dayNames: [ "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" ], + dayNamesShort: [ "周日", "周一", "周二", "周三", "周四", "周五", "周六" ], + dayNamesMin: [ "日", "一", "二", "三", "四", "五", "六" ], weekHeader: "周", dateFormat: "yy-mm-dd", firstDay: 1, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "zh-CN" ] ); return datepicker.regional[ "zh-CN" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-zh-HK.js b/lib/web/jquery/ui-modules/i18n/datepicker-zh-HK.js index 27f02bc6598b2..80d5c7690bdd2 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-zh-HK.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-zh-HK.js @@ -1,6 +1,8 @@ /* Chinese initialisation for the jQuery UI date picker plugin. */ /* Written by SCCY (samuelcychan@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,20 +12,21 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "zh-HK" ] = { closeText: "關閉", prevText: "<上月", nextText: "下月>", currentText: "今天", - monthNames: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - monthNamesShort: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - dayNames: [ "星期日","星期一","星期二","星期三","星期四","星期五","星期六" ], - dayNamesShort: [ "周日","周一","周二","周三","周四","周五","周六" ], - dayNamesMin: [ "日","一","二","三","四","五","六" ], + monthNames: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + monthNamesShort: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + dayNames: [ "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" ], + dayNamesShort: [ "周日", "周一", "周二", "周三", "周四", "周五", "周六" ], + dayNamesMin: [ "日", "一", "二", "三", "四", "五", "六" ], weekHeader: "周", dateFormat: "dd-mm-yy", firstDay: 0, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "zh-HK" ] ); return datepicker.regional[ "zh-HK" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/i18n/datepicker-zh-TW.js b/lib/web/jquery/ui-modules/i18n/datepicker-zh-TW.js index c20754bd51e85..9e79a45cad69e 100644 --- a/lib/web/jquery/ui-modules/i18n/datepicker-zh-TW.js +++ b/lib/web/jquery/ui-modules/i18n/datepicker-zh-TW.js @@ -1,6 +1,8 @@ /* Chinese initialisation for the jQuery UI date picker plugin. */ /* Written by Ressol (ressol@gmail.com). */ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -10,21 +12,22 @@ // Browser globals factory( jQuery.datepicker ); } -}( function( datepicker ) { +} )( function( datepicker ) { +"use strict"; datepicker.regional[ "zh-TW" ] = { closeText: "關閉", - prevText: "<上月", - nextText: "下月>", + prevText: "<上個月", + nextText: "下個月>", currentText: "今天", - monthNames: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - monthNamesShort: [ "一月","二月","三月","四月","五月","六月", - "七月","八月","九月","十月","十一月","十二月" ], - dayNames: [ "星期日","星期一","星期二","星期三","星期四","星期五","星期六" ], - dayNamesShort: [ "周日","周一","周二","周三","周四","周五","周六" ], - dayNamesMin: [ "日","一","二","三","四","五","六" ], - weekHeader: "周", + monthNames: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + monthNamesShort: [ "一月", "二月", "三月", "四月", "五月", "六月", + "七月", "八月", "九月", "十月", "十一月", "十二月" ], + dayNames: [ "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" ], + dayNamesShort: [ "週日", "週一", "週二", "週三", "週四", "週五", "週六" ], + dayNamesMin: [ "日", "一", "二", "三", "四", "五", "六" ], + weekHeader: "週", dateFormat: "yy/mm/dd", firstDay: 1, isRTL: false, @@ -34,4 +37,4 @@ datepicker.setDefaults( datepicker.regional[ "zh-TW" ] ); return datepicker.regional[ "zh-TW" ]; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/ie.js b/lib/web/jquery/ui-modules/ie.js index 1ce6a8450d575..1754b94a9f2ee 100644 --- a/lib/web/jquery/ui-modules/ie.js +++ b/lib/web/jquery/ui-modules/ie.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,8 +10,9 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; // This file is deprecated return $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/jquery-patch.js b/lib/web/jquery/ui-modules/jquery-patch.js new file mode 100644 index 0000000000000..534406eda8ae6 --- /dev/null +++ b/lib/web/jquery/ui-modules/jquery-patch.js @@ -0,0 +1,89 @@ +/*! + * jQuery UI Support for jQuery core 1.8.x and newer 1.13.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + */ + +//>>label: jQuery 1.8+ Support +//>>group: Core +//>>description: Support version 1.8.x and newer of jQuery core + +( function( factory ) { +"use strict"; + + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define( [ "jquery", "./version" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +} )( function( $ ) { +"use strict"; + +// Support: jQuery 1.9.x or older +// $.expr[ ":" ] is deprecated. +if ( !$.expr.pseudos ) { + $.expr.pseudos = $.expr[ ":" ]; +} + +// Support: jQuery 1.11.x or older +// $.unique has been renamed to $.uniqueSort +if ( !$.uniqueSort ) { + $.uniqueSort = $.unique; +} + +// Support: jQuery 2.2.x or older. +// This method has been defined in jQuery 3.0.0. +// Code from https://github.com/jquery/jquery/blob/e539bac79e666bba95bba86d690b4e609dca2286/src/selector/escapeSelector.js +if ( !$.escapeSelector ) { + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + + var fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }; + + $.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); + }; +} + +// Support: jQuery 3.4.x or older +// These methods have been defined in jQuery 3.5.0. +if ( !$.fn.even || !$.fn.odd ) { + $.fn.extend( { + even: function() { + return this.filter( function( i ) { + return i % 2 === 0; + } ); + }, + odd: function() { + return this.filter( function( i ) { + return i % 2 === 1; + } ); + } + } ); +} + +} ); diff --git a/lib/web/jquery/ui-modules/jquery-var-for-color.js b/lib/web/jquery/ui-modules/jquery-var-for-color.js new file mode 100644 index 0000000000000..f37ed56dab732 --- /dev/null +++ b/lib/web/jquery/ui-modules/jquery-var-for-color.js @@ -0,0 +1,22 @@ +( function( factory ) { + "use strict"; + + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define( [ "jquery", "./version" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +} )( function( $ ) { + "use strict"; + +// Create a local jQuery because jQuery Color relies on it and the +// global may not exist with AMD and a custom build (#10199). +// This module is a noop if used as a regular AMD module. +// eslint-disable-next-line no-unused-vars +var jQuery = $; + +} ); diff --git a/lib/web/jquery/ui-modules/keycode.js b/lib/web/jquery/ui-modules/keycode.js index c02a6df52b174..bb211a11bed89 100644 --- a/lib/web/jquery/ui-modules/keycode.js +++ b/lib/web/jquery/ui-modules/keycode.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Keycode 1.12.1 + * jQuery UI Keycode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,7 +24,9 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; + return $.ui.keyCode = { BACKSPACE: 8, COMMA: 188, @@ -42,4 +46,4 @@ return $.ui.keyCode = { UP: 38 }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/labels.js b/lib/web/jquery/ui-modules/labels.js index 57ab75267f524..5efddfe55d51e 100644 --- a/lib/web/jquery/ui-modules/labels.js +++ b/lib/web/jquery/ui-modules/labels.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Labels 1.12.1 + * jQuery UI Labels 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,20 +13,27 @@ //>>docs: http://api.jqueryui.com/labels/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. - define( [ "jquery", "./version", "./escape-selector" ], factory ); + define( [ "jquery", "./version" ], factory ); } else { // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; return $.fn.labels = function() { var ancestor, selector, id, labels, ancestors; + if ( !this.length ) { + return this.pushStack( [] ); + } + // Check control.labels first if ( this[ 0 ].labels && this[ 0 ].labels.length ) { return this.pushStack( this[ 0 ].labels ); @@ -49,7 +56,7 @@ return $.fn.labels = function() { ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); // Create a selector for the label based on the id - selector = "label[for='" + $.ui.escapeSelector( id ) + "']"; + selector = "label[for='" + $.escapeSelector( id ) + "']"; labels = labels.add( ancestors.find( selector ).addBack( selector ) ); @@ -59,4 +66,4 @@ return $.fn.labels = function() { return this.pushStack( labels ); }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/plugin.js b/lib/web/jquery/ui-modules/plugin.js index b282de7c6ddf3..1e46017ad4a44 100644 --- a/lib/web/jquery/ui-modules/plugin.js +++ b/lib/web/jquery/ui-modules/plugin.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,7 +10,8 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; // $.ui.plugin is deprecated. Use $.widget() extensions instead. return $.ui.plugin = { @@ -41,4 +44,4 @@ return $.ui.plugin = { } }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/position.js b/lib/web/jquery/ui-modules/position.js index 9aa8221695bbf..b8773b0e97219 100644 --- a/lib/web/jquery/ui-modules/position.js +++ b/lib/web/jquery/ui-modules/position.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Position 1.12.1 + * jQuery UI Position 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -16,6 +16,8 @@ //>>demos: http://jqueryui.com/position/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -25,7 +27,9 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; + ( function() { var cachedScrollbarWidth, max = Math.max, @@ -48,6 +52,10 @@ function parseCss( element, property ) { return parseInt( $.css( element, property ), 10 ) || 0; } +function isWindow( obj ) { + return obj != null && obj === obj.window; +} + function getDimensions( elem ) { var raw = elem[ 0 ]; if ( raw.nodeType === 9 ) { @@ -57,7 +65,7 @@ function getDimensions( elem ) { offset: { top: 0, left: 0 } }; } - if ( $.isWindow( raw ) ) { + if ( isWindow( raw ) ) { return { width: elem.width(), height: elem.height(), @@ -84,9 +92,9 @@ $.position = { return cachedScrollbarWidth; } var w1, w2, - div = $( "<div " + - "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" + - "<div style='height:100px;width:auto;'></div></div>" ), + div = $( "<div style=" + + "'display:block;position:absolute;width:200px;height:200px;overflow:hidden;'>" + + "<div style='height:300px;width:auto;'></div></div>" ), innerDiv = div.children()[ 0 ]; $( "body" ).append( div ); @@ -119,12 +127,12 @@ $.position = { }, getWithinInfo: function( element ) { var withinElement = $( element || window ), - isWindow = $.isWindow( withinElement[ 0 ] ), + isElemWindow = isWindow( withinElement[ 0 ] ), isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, - hasOffset = !isWindow && !isDocument; + hasOffset = !isElemWindow && !isDocument; return { element: withinElement, - isWindow: isWindow, + isWindow: isElemWindow, isDocument: isDocument, offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, scrollLeft: withinElement.scrollLeft(), @@ -144,7 +152,12 @@ $.fn.position = function( options ) { options = $.extend( {}, options ); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, - target = $( options.of ), + + // Make sure string options are treated as CSS selectors + target = typeof options.of === "string" ? + $( document ).find( options.of ) : + $( options.of ), + within = $.position.getWithinInfo( options.within ), scrollInfo = $.position.getScrollInfo( within ), collision = ( options.collision || "flip" ).split( " " ), @@ -495,4 +508,4 @@ $.ui.position = { return $.ui.position; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/safe-active-element.js b/lib/web/jquery/ui-modules/safe-active-element.js index 4d04add5ffbfd..9d6968e08fb36 100644 --- a/lib/web/jquery/ui-modules/safe-active-element.js +++ b/lib/web/jquery/ui-modules/safe-active-element.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,7 +10,9 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; + return $.ui.safeActiveElement = function( document ) { var activeElement; @@ -37,4 +41,4 @@ return $.ui.safeActiveElement = function( document ) { return activeElement; }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/safe-blur.js b/lib/web/jquery/ui-modules/safe-blur.js index 52587826807ab..c0b3b8a1e09df 100644 --- a/lib/web/jquery/ui-modules/safe-blur.js +++ b/lib/web/jquery/ui-modules/safe-blur.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,7 +10,9 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; + return $.ui.safeBlur = function( element ) { // Support: IE9 - 10 only @@ -18,4 +22,4 @@ return $.ui.safeBlur = function( element ) { } }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/scroll-parent.js b/lib/web/jquery/ui-modules/scroll-parent.js index dfe820b625769..2bc92c4e4b743 100644 --- a/lib/web/jquery/ui-modules/scroll-parent.js +++ b/lib/web/jquery/ui-modules/scroll-parent.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Scroll Parent 1.12.1 + * jQuery UI Scroll Parent 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/scrollParent/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,7 +24,8 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; return $.fn.scrollParent = function( includeHidden ) { var position = this.css( "position" ), @@ -42,4 +45,4 @@ return $.fn.scrollParent = function( includeHidden ) { scrollParent; }; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/tabbable.js b/lib/web/jquery/ui-modules/tabbable.js index d3ceafd90c4b4..4abd590863b18 100644 --- a/lib/web/jquery/ui-modules/tabbable.js +++ b/lib/web/jquery/ui-modules/tabbable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Tabbable 1.12.1 + * jQuery UI Tabbable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/tabbable-selector/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,9 +24,10 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; -return $.extend( $.expr[ ":" ], { +return $.extend( $.expr.pseudos, { tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), hasTabindex = tabIndex != null; @@ -32,4 +35,4 @@ return $.extend( $.expr[ ":" ], { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/unique-id.js b/lib/web/jquery/ui-modules/unique-id.js index 9e45f0afa3002..81eaecb3c91ae 100644 --- a/lib/web/jquery/ui-modules/unique-id.js +++ b/lib/web/jquery/ui-modules/unique-id.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Unique ID 1.12.1 + * jQuery UI Unique ID 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/uniqueId/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -22,7 +24,8 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; return $.fn.extend( { uniqueId: ( function() { @@ -46,4 +49,4 @@ return $.fn.extend( { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/vendor/jquery-color/LICENSE.txt b/lib/web/jquery/ui-modules/vendor/jquery-color/LICENSE.txt new file mode 100644 index 0000000000000..6a864b8baf06d --- /dev/null +++ b/lib/web/jquery/ui-modules/vendor/jquery-color/LICENSE.txt @@ -0,0 +1,43 @@ +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/jquery-color + +The following license applies to all parts of jQuery Color except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the README. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +All files located in the node_modules and external directories are +externally maintained libraries used by this software which have their +own licenses; we recommend you read them, as their terms may differ from +the terms above. diff --git a/lib/web/jquery/ui-modules/vendor/jquery-color/jquery.color.js b/lib/web/jquery/ui-modules/vendor/jquery-color/jquery.color.js new file mode 100644 index 0000000000000..c291d96ae8515 --- /dev/null +++ b/lib/web/jquery/ui-modules/vendor/jquery-color/jquery.color.js @@ -0,0 +1,722 @@ +/*! + * jQuery Color Animations v2.2.0 + * https://github.com/jquery/jquery-color + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * Date: Sun May 10 09:02:36 2020 +0200 + */ + +( function( root, factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define( [ "jquery" ], factory ); + } else if ( typeof exports === "object" ) { + module.exports = factory( require( "jquery" ) ); + } else { + factory( root.jQuery ); + } +} )( this, function( jQuery, undefined ) { + + var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + + "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", + + class2type = {}, + toString = class2type.toString, + + // plusequals test for += 100 -= 100 + rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, + + // a set of RE's that can match strings and generate color tuples. + stringParsers = [ { + re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ], + execResult[ 3 ], + execResult[ 4 ] + ]; + } + }, { + re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ] * 2.55, + execResult[ 2 ] * 2.55, + execResult[ 3 ] * 2.55, + execResult[ 4 ] + ]; + } + }, { + + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})?/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ], 16 ), + execResult[ 4 ] ? + ( parseInt( execResult[ 4 ], 16 ) / 255 ).toFixed( 2 ) : + 1 + ]; + } + }, { + + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])?/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ), + execResult[ 4 ] ? + ( parseInt( execResult[ 4 ] + execResult[ 4 ], 16 ) / 255 ) + .toFixed( 2 ) : + 1 + ]; + } + }, { + re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + space: "hsla", + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ] / 100, + execResult[ 3 ] / 100, + execResult[ 4 ] + ]; + } + } ], + + // jQuery.Color( ) + color = jQuery.Color = function( color, green, blue, alpha ) { + return new jQuery.Color.fn.parse( color, green, blue, alpha ); + }, + spaces = { + rgba: { + props: { + red: { + idx: 0, + type: "byte" + }, + green: { + idx: 1, + type: "byte" + }, + blue: { + idx: 2, + type: "byte" + } + } + }, + + hsla: { + props: { + hue: { + idx: 0, + type: "degrees" + }, + saturation: { + idx: 1, + type: "percent" + }, + lightness: { + idx: 2, + type: "percent" + } + } + } + }, + propTypes = { + "byte": { + floor: true, + max: 255 + }, + "percent": { + max: 1 + }, + "degrees": { + mod: 360, + floor: true + } + }, + support = color.support = {}, + + // element for support tests + supportElem = jQuery( "<p>" )[ 0 ], + + // colors = jQuery.Color.names + colors, + + // local aliases of functions called often + each = jQuery.each; + +// determine rgba support immediately +supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; +support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; + +// define cache name and alpha properties +// for rgba and hsla spaces +each( spaces, function( spaceName, space ) { + space.cache = "_" + spaceName; + space.props.alpha = { + idx: 3, + type: "percent", + def: 1 + }; +} ); + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function getType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + return typeof obj === "object" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} + +function clamp( value, prop, allowEmpty ) { + var type = propTypes[ prop.type ] || {}; + + if ( value == null ) { + return ( allowEmpty || !prop.def ) ? null : prop.def; + } + + // ~~ is an short way of doing floor for positive numbers + value = type.floor ? ~~value : parseFloat( value ); + + // IE will pass in empty strings as value for alpha, + // which will hit this case + if ( isNaN( value ) ) { + return prop.def; + } + + if ( type.mod ) { + + // we add mod before modding to make sure that negatives values + // get converted properly: -10 -> 350 + return ( value + type.mod ) % type.mod; + } + + // for now all property types without mod have min and max + return Math.min( type.max, Math.max( 0, value ) ); +} + +function stringParse( string ) { + var inst = color(), + rgba = inst._rgba = []; + + string = string.toLowerCase(); + + each( stringParsers, function( _i, parser ) { + var parsed, + match = parser.re.exec( string ), + values = match && parser.parse( match ), + spaceName = parser.space || "rgba"; + + if ( values ) { + parsed = inst[ spaceName ]( values ); + + // if this was an rgba parse the assignment might happen twice + // oh well.... + inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; + rgba = inst._rgba = parsed._rgba; + + // exit each( stringParsers ) here because we matched + return false; + } + } ); + + // Found a stringParser that handled it + if ( rgba.length ) { + + // if this came from a parsed string, force "transparent" when alpha is 0 + // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) + if ( rgba.join() === "0,0,0,0" ) { + jQuery.extend( rgba, colors.transparent ); + } + return inst; + } + + // named colors + return colors[ string ]; +} + +color.fn = jQuery.extend( color.prototype, { + parse: function( red, green, blue, alpha ) { + if ( red === undefined ) { + this._rgba = [ null, null, null, null ]; + return this; + } + if ( red.jquery || red.nodeType ) { + red = jQuery( red ).css( green ); + green = undefined; + } + + var inst = this, + type = getType( red ), + rgba = this._rgba = []; + + // more than 1 argument specified - assume ( red, green, blue, alpha ) + if ( green !== undefined ) { + red = [ red, green, blue, alpha ]; + type = "array"; + } + + if ( type === "string" ) { + return this.parse( stringParse( red ) || colors._default ); + } + + if ( type === "array" ) { + each( spaces.rgba.props, function( _key, prop ) { + rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); + } ); + return this; + } + + if ( type === "object" ) { + if ( red instanceof color ) { + each( spaces, function( _spaceName, space ) { + if ( red[ space.cache ] ) { + inst[ space.cache ] = red[ space.cache ].slice(); + } + } ); + } else { + each( spaces, function( _spaceName, space ) { + var cache = space.cache; + each( space.props, function( key, prop ) { + + // if the cache doesn't exist, and we know how to convert + if ( !inst[ cache ] && space.to ) { + + // if the value was null, we don't need to copy it + // if the key was alpha, we don't need to copy it either + if ( key === "alpha" || red[ key ] == null ) { + return; + } + inst[ cache ] = space.to( inst._rgba ); + } + + // this is the only case where we allow nulls for ALL properties. + // call clamp with alwaysAllowEmpty + inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); + } ); + + // everything defined but alpha? + if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { + + // use the default of 1 + if ( inst[ cache ][ 3 ] == null ) { + inst[ cache ][ 3 ] = 1; + } + + if ( space.from ) { + inst._rgba = space.from( inst[ cache ] ); + } + } + } ); + } + return this; + } + }, + is: function( compare ) { + var is = color( compare ), + same = true, + inst = this; + + each( spaces, function( _, space ) { + var localCache, + isCache = is[ space.cache ]; + if ( isCache ) { + localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; + each( space.props, function( _, prop ) { + if ( isCache[ prop.idx ] != null ) { + same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); + return same; + } + } ); + } + return same; + } ); + return same; + }, + _space: function() { + var used = [], + inst = this; + each( spaces, function( spaceName, space ) { + if ( inst[ space.cache ] ) { + used.push( spaceName ); + } + } ); + return used.pop(); + }, + transition: function( other, distance ) { + var end = color( other ), + spaceName = end._space(), + space = spaces[ spaceName ], + startColor = this.alpha() === 0 ? color( "transparent" ) : this, + start = startColor[ space.cache ] || space.to( startColor._rgba ), + result = start.slice(); + + end = end[ space.cache ]; + each( space.props, function( _key, prop ) { + var index = prop.idx, + startValue = start[ index ], + endValue = end[ index ], + type = propTypes[ prop.type ] || {}; + + // if null, don't override start value + if ( endValue === null ) { + return; + } + + // if null - use end + if ( startValue === null ) { + result[ index ] = endValue; + } else { + if ( type.mod ) { + if ( endValue - startValue > type.mod / 2 ) { + startValue += type.mod; + } else if ( startValue - endValue > type.mod / 2 ) { + startValue -= type.mod; + } + } + result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); + } + } ); + return this[ spaceName ]( result ); + }, + blend: function( opaque ) { + + // if we are already opaque - return ourself + if ( this._rgba[ 3 ] === 1 ) { + return this; + } + + var rgb = this._rgba.slice(), + a = rgb.pop(), + blend = color( opaque )._rgba; + + return color( jQuery.map( rgb, function( v, i ) { + return ( 1 - a ) * blend[ i ] + a * v; + } ) ); + }, + toRgbaString: function() { + var prefix = "rgba(", + rgba = jQuery.map( this._rgba, function( v, i ) { + if ( v != null ) { + return v; + } + return i > 2 ? 1 : 0; + } ); + + if ( rgba[ 3 ] === 1 ) { + rgba.pop(); + prefix = "rgb("; + } + + return prefix + rgba.join() + ")"; + }, + toHslaString: function() { + var prefix = "hsla(", + hsla = jQuery.map( this.hsla(), function( v, i ) { + if ( v == null ) { + v = i > 2 ? 1 : 0; + } + + // catch 1 and 2 + if ( i && i < 3 ) { + v = Math.round( v * 100 ) + "%"; + } + return v; + } ); + + if ( hsla[ 3 ] === 1 ) { + hsla.pop(); + prefix = "hsl("; + } + return prefix + hsla.join() + ")"; + }, + toHexString: function( includeAlpha ) { + var rgba = this._rgba.slice(), + alpha = rgba.pop(); + + if ( includeAlpha ) { + rgba.push( ~~( alpha * 255 ) ); + } + + return "#" + jQuery.map( rgba, function( v ) { + + // default to 0 when nulls exist + v = ( v || 0 ).toString( 16 ); + return v.length === 1 ? "0" + v : v; + } ).join( "" ); + }, + toString: function() { + return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); + } +} ); +color.fn.parse.prototype = color.fn; + +// hsla conversions adapted from: +// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 + +function hue2rgb( p, q, h ) { + h = ( h + 1 ) % 1; + if ( h * 6 < 1 ) { + return p + ( q - p ) * h * 6; + } + if ( h * 2 < 1 ) { + return q; + } + if ( h * 3 < 2 ) { + return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; + } + return p; +} + +spaces.hsla.to = function( rgba ) { + if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { + return [ null, null, null, rgba[ 3 ] ]; + } + var r = rgba[ 0 ] / 255, + g = rgba[ 1 ] / 255, + b = rgba[ 2 ] / 255, + a = rgba[ 3 ], + max = Math.max( r, g, b ), + min = Math.min( r, g, b ), + diff = max - min, + add = max + min, + l = add * 0.5, + h, s; + + if ( min === max ) { + h = 0; + } else if ( r === max ) { + h = ( 60 * ( g - b ) / diff ) + 360; + } else if ( g === max ) { + h = ( 60 * ( b - r ) / diff ) + 120; + } else { + h = ( 60 * ( r - g ) / diff ) + 240; + } + + // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% + // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) + if ( diff === 0 ) { + s = 0; + } else if ( l <= 0.5 ) { + s = diff / add; + } else { + s = diff / ( 2 - add ); + } + return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; +}; + +spaces.hsla.from = function( hsla ) { + if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { + return [ null, null, null, hsla[ 3 ] ]; + } + var h = hsla[ 0 ] / 360, + s = hsla[ 1 ], + l = hsla[ 2 ], + a = hsla[ 3 ], + q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, + p = 2 * l - q; + + return [ + Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), + Math.round( hue2rgb( p, q, h ) * 255 ), + Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), + a + ]; +}; + + +each( spaces, function( spaceName, space ) { + var props = space.props, + cache = space.cache, + to = space.to, + from = space.from; + + // makes rgba() and hsla() + color.fn[ spaceName ] = function( value ) { + + // generate a cache for this space if it doesn't exist + if ( to && !this[ cache ] ) { + this[ cache ] = to( this._rgba ); + } + if ( value === undefined ) { + return this[ cache ].slice(); + } + + var ret, + type = getType( value ), + arr = ( type === "array" || type === "object" ) ? value : arguments, + local = this[ cache ].slice(); + + each( props, function( key, prop ) { + var val = arr[ type === "object" ? key : prop.idx ]; + if ( val == null ) { + val = local[ prop.idx ]; + } + local[ prop.idx ] = clamp( val, prop ); + } ); + + if ( from ) { + ret = color( from( local ) ); + ret[ cache ] = local; + return ret; + } else { + return color( local ); + } + }; + + // makes red() green() blue() alpha() hue() saturation() lightness() + each( props, function( key, prop ) { + + // alpha is included in more than one space + if ( color.fn[ key ] ) { + return; + } + color.fn[ key ] = function( value ) { + var local, cur, match, fn, + vtype = getType( value ); + + if ( key === "alpha" ) { + fn = this._hsla ? "hsla" : "rgba"; + } else { + fn = spaceName; + } + local = this[ fn ](); + cur = local[ prop.idx ]; + + if ( vtype === "undefined" ) { + return cur; + } + + if ( vtype === "function" ) { + value = value.call( this, cur ); + vtype = getType( value ); + } + if ( value == null && prop.empty ) { + return this; + } + if ( vtype === "string" ) { + match = rplusequals.exec( value ); + if ( match ) { + value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); + } + } + local[ prop.idx ] = value; + return this[ fn ]( local ); + }; + } ); +} ); + +// add cssHook and .fx.step function for each named hook. +// accept a space separated string of properties +color.hook = function( hook ) { + var hooks = hook.split( " " ); + each( hooks, function( _i, hook ) { + jQuery.cssHooks[ hook ] = { + set: function( elem, value ) { + var parsed, curElem, + backgroundColor = ""; + + if ( value !== "transparent" && ( getType( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { + value = color( parsed || value ); + if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { + curElem = hook === "backgroundColor" ? elem.parentNode : elem; + while ( + ( backgroundColor === "" || backgroundColor === "transparent" ) && + curElem && curElem.style + ) { + try { + backgroundColor = jQuery.css( curElem, "backgroundColor" ); + curElem = curElem.parentNode; + } catch ( e ) { + } + } + + value = value.blend( backgroundColor && backgroundColor !== "transparent" ? + backgroundColor : + "_default" ); + } + + value = value.toRgbaString(); + } + try { + elem.style[ hook ] = value; + } catch ( e ) { + + // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' + } + } + }; + jQuery.fx.step[ hook ] = function( fx ) { + if ( !fx.colorInit ) { + fx.start = color( fx.elem, hook ); + fx.end = color( fx.end ); + fx.colorInit = true; + } + jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); + }; + } ); + +}; + +color.hook( stepHooks ); + +jQuery.cssHooks.borderColor = { + expand: function( value ) { + var expanded = {}; + + each( [ "Top", "Right", "Bottom", "Left" ], function( _i, part ) { + expanded[ "border" + part + "Color" ] = value; + } ); + return expanded; + } +}; + +// Basic color names only. +// Usage of any of the other color names requires adding yourself or including +// jquery.color.svg-names.js. +colors = jQuery.Color.names = { + + // 4.1. Basic color keywords + aqua: "#00ffff", + black: "#000000", + blue: "#0000ff", + fuchsia: "#ff00ff", + gray: "#808080", + green: "#008000", + lime: "#00ff00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + purple: "#800080", + red: "#ff0000", + silver: "#c0c0c0", + teal: "#008080", + white: "#ffffff", + yellow: "#ffff00", + + // 4.2.3. "transparent" color keyword + transparent: [ null, null, null, 0 ], + + _default: "#ffffff" +}; + +} ); diff --git a/lib/web/jquery/ui-modules/version.js b/lib/web/jquery/ui-modules/version.js index e7505f5a3d2d2..267dfe9bba799 100644 --- a/lib/web/jquery/ui-modules/version.js +++ b/lib/web/jquery/ui-modules/version.js @@ -1,4 +1,6 @@ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -8,10 +10,11 @@ // Browser globals factory( jQuery ); } -} ( function( $ ) { +} )( function( $ ) { +"use strict"; $.ui = $.ui || {}; -return $.ui.version = "1.12.1"; +return $.ui.version = "1.13.0"; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widget.js b/lib/web/jquery/ui-modules/widget.js index cac9413190ec9..08c69ecd549a4 100644 --- a/lib/web/jquery/ui-modules/widget.js +++ b/lib/web/jquery/ui-modules/widget.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Widget 1.12.1 + * jQuery UI Widget 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/widget/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -23,25 +25,23 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; var widgetUuid = 0; +var widgetHasOwnProperty = Array.prototype.hasOwnProperty; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { - try { - - // Only trigger remove when necessary to save time - events = $._data( elem, "events" ); - if ( events && events.remove ) { - $( elem ).triggerHandler( "remove" ); - } - // Http://bugs.jquery.com/ticket/8235 - } catch ( e ) {} + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } } orig( elems ); }; @@ -63,12 +63,12 @@ $.widget = function( name, base, prototype ) { base = $.Widget; } - if ( $.isArray( prototype ) ) { + if ( Array.isArray( prototype ) ) { prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); } // Create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; @@ -77,7 +77,7 @@ $.widget = function( name, base, prototype ) { constructor = $[ namespace ][ name ] = function( options, element ) { // Allow instantiation without "new" keyword - if ( !this._createWidget ) { + if ( !this || !this._createWidget ) { return new constructor( options, element ); } @@ -108,7 +108,7 @@ $.widget = function( name, base, prototype ) { // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { - if ( !$.isFunction( value ) ) { + if ( typeof value !== "function" ) { proxiedPrototype[ prop ] = value; return; } @@ -187,7 +187,7 @@ $.widget.extend = function( target ) { for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { @@ -236,7 +236,8 @@ $.widget.bridge = function( name, object ) { "attempted to call method '" + options + "'" ); } - if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + if ( typeof instance[ options ] !== "function" || + options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } @@ -497,12 +498,30 @@ $.Widget.prototype = { classes: this.options.classes || {} }, options ); + function bindRemoveEvent() { + options.element.each( function( _, element ) { + var isTracked = $.map( that.classesElementLookup, function( elements ) { + return elements; + } ) + .some( function( elements ) { + return elements.is( element ); + } ); + + if ( !isTracked ) { + that._on( $( element ), { + remove: "_untrackClassesElement" + } ); + } + } ); + } + function processClassString( classes, checkOption ) { var current, i; for ( i = 0; i < classes.length; i++ ) { current = that.classesElementLookup[ classes[ i ] ] || $(); if ( options.add ) { - current = $( $.unique( current.get().concat( options.element.get() ) ) ); + bindRemoveEvent(); + current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) ); } else { current = $( current.not( options.element ).get() ); } @@ -514,10 +533,6 @@ $.Widget.prototype = { } } - this._on( options.element, { - "remove": "_untrackClassesElement" - } ); - if ( options.keys ) { processClassString( options.keys.match( /\S+/g ) || [], true ); } @@ -535,6 +550,8 @@ $.Widget.prototype = { that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); } } ); + + this._off( $( event.target ) ); }, _removeClass: function( element, keys, extra ) { @@ -615,7 +632,7 @@ $.Widget.prototype = { _off: function( element, eventName ) { eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; - element.off( eventName ).off( eventName ); + element.off( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); @@ -681,7 +698,7 @@ $.Widget.prototype = { } this.element.trigger( event, data ); - return !( $.isFunction( callback ) && + return !( typeof callback === "function" && callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } @@ -703,6 +720,8 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; + } else if ( options === true ) { + options = {}; } hasOptions = !$.isEmptyObject( options ); @@ -730,4 +749,4 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { return $.widget; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/accordion.js b/lib/web/jquery/ui-modules/widgets/accordion.js index 4b3a54d0fd745..49a7f787492cd 100644 --- a/lib/web/jquery/ui-modules/widgets/accordion.js +++ b/lib/web/jquery/ui-modules/widgets/accordion.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Accordion 1.12.1 + * jQuery UI Accordion 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9,9 +9,9 @@ //>>label: Accordion //>>group: Widgets -// jscs:disable maximumLineLength +/* eslint-disable max-len */ //>>description: Displays collapsible content panels for presenting information in a limited amount of space. -// jscs:enable maximumLineLength +/* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/accordion/ //>>demos: http://jqueryui.com/accordion/ //>>css.structure: ../../themes/base/core.css @@ -19,6 +19,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -34,10 +36,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.accordion", { - version: "1.12.1", + version: "1.13.0", options: { active: 0, animate: {}, @@ -48,7 +51,9 @@ return $.widget( "ui.accordion", { }, collapsible: false, event: "click", - header: "> li > :first-child, > :not(li):even", + header: function( elem ) { + return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() ); + }, heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", @@ -279,7 +284,11 @@ return $.widget( "ui.accordion", { var prevHeaders = this.headers, prevPanels = this.panels; - this.headers = this.element.find( this.options.header ); + if ( typeof this.options.header === "function" ) { + this.headers = this.options.header( this.element ); + } else { + this.headers = this.element.find( this.options.header ); + } this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", "ui-state-default" ); @@ -610,4 +619,4 @@ return $.widget( "ui.accordion", { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/autocomplete.js b/lib/web/jquery/ui-modules/widgets/autocomplete.js index f2bfb2d89aecd..ea31b7e8d9c65 100644 --- a/lib/web/jquery/ui-modules/widgets/autocomplete.js +++ b/lib/web/jquery/ui-modules/widgets/autocomplete.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Autocomplete 1.12.1 + * jQuery UI Autocomplete 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -34,10 +36,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.autocomplete", { - version: "1.12.1", + version: "1.13.0", defaultElement: "<input>", options: { appendTo: null, @@ -200,11 +203,6 @@ $.widget( "ui.autocomplete", { this.previous = this._value(); }, blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; - } - clearTimeout( this.searching ); this.close( event ); this._change( event ); @@ -220,31 +218,24 @@ $.widget( "ui.autocomplete", { role: null } ) .hide() + + // Support: IE 11 only, Edge <= 14 + // For other browsers, we preventDefault() on the mousedown event + // to keep the dropdown from taking focus from the input. This doesn't + // work for IE/Edge, causing problems with selection and scrolling (#9638) + // Happily, IE and Edge support an "unselectable" attribute that + // prevents an element from receiving focus, exactly what we want here. + .attr( { + "unselectable": "on" + } ) .menu( "instance" ); this._addClass( this.menu.element, "ui-autocomplete", "ui-front" ); this._on( this.menu.element, { mousedown: function( event ) { - // prevent moving focus out of the text field + // Prevent moving focus out of the text field event.preventDefault(); - - // IE doesn't prevent moving focus even with event.preventDefault() - // so we set a flag to know when we should ignore the blur event - this.cancelBlur = true; - this._delay( function() { - delete this.cancelBlur; - - // Support: IE 8 only - // Right clicking a menu item or selecting text from the menu items will - // result in focus moving out of the input. However, we've already received - // and ignored the blur event because of the cancelBlur flag set above. So - // we restore focus to ensure that the menu closes properly based on the user's - // next actions. - if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { - this.element.trigger( "focus" ); - } - } ); }, menufocus: function( event, ui ) { var label, item; @@ -275,7 +266,7 @@ $.widget( "ui.autocomplete", { // Announce the value in the liveRegion label = ui.item.attr( "aria-label" ) || item.value; - if ( label && $.trim( label ).length ) { + if ( label && String.prototype.trim.call( label ).length ) { this.liveRegion.children().hide(); $( "<div>" ).text( label ).appendTo( this.liveRegion ); } @@ -387,7 +378,7 @@ $.widget( "ui.autocomplete", { _initSource: function() { var array, url, that = this; - if ( $.isArray( this.options.source ) ) { + if ( Array.isArray( this.options.source ) ) { array = this.options.source; this.source = function( request, response ) { response( $.ui.autocomplete.filter( array, request.term ) ); @@ -459,7 +450,7 @@ $.widget( "ui.autocomplete", { _response: function() { var index = ++this.requestIndex; - return $.proxy( function( content ) { + return function( content ) { if ( index === this.requestIndex ) { this.__response( content ); } @@ -468,7 +459,7 @@ $.widget( "ui.autocomplete", { if ( !this.pending ) { this._removeClass( "ui-autocomplete-loading" ); } - }, this ); + }.bind( this ); }, __response: function( content ) { @@ -628,7 +619,7 @@ $.widget( "ui.autocomplete", { var editable = element.prop( "contentEditable" ); if ( editable === "inherit" ) { - return this._isContentEditable( element.parent() ); + return this._isContentEditable( element.parent() ); } return editable === "true"; @@ -679,4 +670,4 @@ $.widget( "ui.autocomplete", $.ui.autocomplete, { return $.ui.autocomplete; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/button.js b/lib/web/jquery/ui-modules/widgets/button.js index 62cc43fcd1326..bb667bfb0c369 100644 --- a/lib/web/jquery/ui-modules/widgets/button.js +++ b/lib/web/jquery/ui-modules/widgets/button.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Button 1.12.1 + * jQuery UI Button 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -36,10 +38,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.button", { - version: "1.12.1", + version: "1.13.0", defaultElement: "<button>", options: { classes: { @@ -263,7 +266,7 @@ $.widget( "ui.button", { this._toggleClass( null, "ui-state-disabled", value ); this.element[ 0 ].disabled = value; if ( value ) { - this.element.blur(); + this.element.trigger( "blur" ); } } }, @@ -342,22 +345,82 @@ if ( $.uiBackCompat !== false ) { } ); $.fn.button = ( function( orig ) { - return function() { - if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) || - ( this.length && this[ 0 ].tagName === "INPUT" && ( - this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio" - ) ) ) { - return orig.apply( this, arguments ); - } - if ( !$.ui.checkboxradio ) { - $.error( "Checkboxradio widget missing" ); - } - if ( arguments.length === 0 ) { - return this.checkboxradio( { - "icon": false + return function( options ) { + var isMethodCall = typeof options === "string"; + var args = Array.prototype.slice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? + "button" : + "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on button" + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( typeof instance[ options ] !== "function" || + options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for button" + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? "button" : "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + if ( name === "button" ) { + orig.call( $( this ), options ); + return; + } + + $( this ).checkboxradio( $.extend( { icon: false }, options ) ); + } } ); } - return this.checkboxradio.apply( this, arguments ); + + return returnValue; }; } )( $.fn.button ); @@ -383,4 +446,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.button; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/checkboxradio.js b/lib/web/jquery/ui-modules/widgets/checkboxradio.js index ee70d848bb1a3..f85db18630adb 100644 --- a/lib/web/jquery/ui-modules/widgets/checkboxradio.js +++ b/lib/web/jquery/ui-modules/widgets/checkboxradio.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Checkboxradio 1.12.1 + * jQuery UI Checkboxradio 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -18,12 +18,13 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery", - "../escape-selector", "../form-reset-mixin", "../labels", "../widget" @@ -33,10 +34,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { - version: "1.12.1", + version: "1.13.0", options: { disabled: null, label: null, @@ -115,9 +117,6 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { if ( checked ) { this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" ); - if ( this.icon ) { - this._addClass( this.icon, null, "ui-state-hover" ); - } } this._on( { @@ -152,7 +151,7 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { _getRadioGroup: function() { var group; var name = this.element[ 0 ].name; - var nameSelector = "input[name='" + $.ui.escapeSelector( name ) + "']"; + var nameSelector = "input[name='" + $.escapeSelector( name ) + "']"; if ( !name ) { return $( [] ); @@ -164,7 +163,7 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { // Not inside a form, check all inputs that also are not inside a form group = $( nameSelector ).filter( function() { - return $( this ).form().length === 0; + return $( this )._form().length === 0; } ); } @@ -283,4 +282,4 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { return $.ui.checkboxradio; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/controlgroup.js b/lib/web/jquery/ui-modules/widgets/controlgroup.js index b8234fb4a4702..7bac43761149e 100644 --- a/lib/web/jquery/ui-modules/widgets/controlgroup.js +++ b/lib/web/jquery/ui-modules/widgets/controlgroup.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Controlgroup 1.12.1 + * jQuery UI Controlgroup 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -29,11 +31,13 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; + var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g; return $.widget( "ui.controlgroup", { - version: "1.12.1", + version: "1.13.0", defaultElement: "<div>", options: { direction: "horizontal", @@ -150,7 +154,7 @@ return $.widget( "ui.controlgroup", { } ); } ); - this.childWidgets = $( $.unique( childWidgets ) ); + this.childWidgets = $( $.uniqueSort( childWidgets ) ); this._addClass( this.childWidgets, "ui-controlgroup-item" ); }, @@ -234,7 +238,7 @@ return $.widget( "ui.controlgroup", { var result = {}; $.each( classes, function( key ) { var current = instance.options.classes[ key ] || ""; - current = $.trim( current.replace( controlgroupCornerRegex, "" ) ); + current = String.prototype.trim.call( current.replace( controlgroupCornerRegex, "" ) ); result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " ); } ); return result; @@ -295,4 +299,4 @@ return $.widget( "ui.controlgroup", { } } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/datepicker.js b/lib/web/jquery/ui-modules/widgets/datepicker.js index 8b6a4f4ecdfa7..45ead4d9c0dea 100644 --- a/lib/web/jquery/ui-modules/widgets/datepicker.js +++ b/lib/web/jquery/ui-modules/widgets/datepicker.js @@ -1,7 +1,6 @@ -// jscs:disable maximumLineLength -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ +/* eslint-disable max-len, camelcase */ /*! - * jQuery UI Datepicker 1.12.1 + * jQuery UI Datepicker 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -19,6 +18,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -32,9 +33,10 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; -$.extend( $.ui, { datepicker: { version: "1.12.1" } } ); +$.extend( $.ui, { datepicker: { version: "1.13.0" } } ); var datepicker_instActive; @@ -62,6 +64,7 @@ function datepicker_getZindex( elem ) { return 0; } + /* Date picker manager. Use the singleton instance of this class, $.datepicker, to interact with the date picker. Settings for (groups of) date pickers are maintained in an instance object, @@ -88,18 +91,20 @@ function Datepicker() { prevText: "Prev", // Display text for previous month link nextText: "Next", // Display text for next month link currentText: "Today", // Display text for current month link - monthNames: [ "January","February","March","April","May","June", - "July","August","September","October","November","December" ], // Names of months for drop-down and formatting + monthNames: [ "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" ], // Names of months for drop-down and formatting monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting - dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday + dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], // Column headings for days starting at Sunday weekHeader: "Wk", // Column header for week of the year dateFormat: "mm/dd/yy", // See format options on parseDate firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... isRTL: false, // True if right-to-left language, false if left-to-right showMonthAfterYear: false, // True if the year select precedes month, false for month then year - yearSuffix: "" // Additional text to append to the year in the month headers + yearSuffix: "", // Additional text to append to the year in the month headers, + selectMonthLabel: "Select month", // Invisible label for month selector + selectYearLabel: "Select year" // Invisible label for year selector }; this._defaults = { // Global defaults for all the date picker instances showOn: "focus", // "focus" for popup on focus, @@ -140,6 +145,7 @@ function Datepicker() { onSelect: null, // Define a callback function when a date is selected onChangeMonthYear: null, // Define a callback function when the month or year is changed onClose: null, // Define a callback function when the datepicker is closed + onUpdateDatepicker: null, // Define a callback function when the datepicker is updated numberOfMonths: 1, // Number of months to show at a time showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) stepMonths: 1, // Number of months to step back/forward @@ -158,6 +164,7 @@ function Datepicker() { } $.extend( Datepicker.prototype, { + /* Class name added to elements to indicate already configured with a date picker. */ markerClassName: "hasDatepicker", @@ -240,7 +247,9 @@ $.extend( Datepicker.prototype, { inst.append.remove(); } if ( appendText ) { - inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" ); + inst.append = $( "<span>" ) + .addClass( this._appendClass ) + .text( appendText ); input[ isRTL ? "before" : "after" ]( inst.append ); } @@ -257,12 +266,32 @@ $.extend( Datepicker.prototype, { if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked buttonText = this._get( inst, "buttonText" ); buttonImage = this._get( inst, "buttonImage" ); - inst.trigger = $( this._get( inst, "buttonImageOnly" ) ? - $( "<img/>" ).addClass( this._triggerClass ). - attr( { src: buttonImage, alt: buttonText, title: buttonText } ) : - $( "<button type='button'></button>" ).addClass( this._triggerClass ). - html( !buttonImage ? buttonText : $( "<img/>" ).attr( - { src:buttonImage, alt:buttonText, title:buttonText } ) ) ); + + if ( this._get( inst, "buttonImageOnly" ) ) { + inst.trigger = $( "<img>" ) + .addClass( this._triggerClass ) + .attr( { + src: buttonImage, + alt: buttonText, + title: buttonText + } ); + } else { + inst.trigger = $( "<button type='button'>" ) + .addClass( this._triggerClass ); + if ( buttonImage ) { + inst.trigger.html( + $( "<img>" ) + .attr( { + src: buttonImage, + alt: buttonText, + title: buttonText + } ) + ); + } else { + inst.trigger.text( buttonText ); + } + } + input[ isRTL ? "before" : "after" ]( inst.trigger ); inst.trigger.on( "click", function() { if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { @@ -408,6 +437,7 @@ $.extend( Datepicker.prototype, { if ( datepicker_instActive === inst ) { datepicker_instActive = null; + this._curInst = null; } }, @@ -427,7 +457,9 @@ $.extend( Datepicker.prototype, { if ( nodeName === "input" ) { target.disabled = false; inst.trigger.filter( "button" ). - each( function() { this.disabled = false; } ).end(). + each( function() { + this.disabled = false; + } ).end(). filter( "img" ).css( { opacity: "1.0", cursor: "" } ); } else if ( nodeName === "div" || nodeName === "span" ) { inline = $target.children( "." + this._inlineClass ); @@ -436,7 +468,11 @@ $.extend( Datepicker.prototype, { prop( "disabled", false ); } this._disabledInputs = $.map( this._disabledInputs, - function( value ) { return ( value === target ? null : value ); } ); // delete entry + + // Delete entry + function( value ) { + return ( value === target ? null : value ); + } ); }, /* Disable the date picker to a jQuery selection. @@ -455,7 +491,9 @@ $.extend( Datepicker.prototype, { if ( nodeName === "input" ) { target.disabled = true; inst.trigger.filter( "button" ). - each( function() { this.disabled = true; } ).end(). + each( function() { + this.disabled = true; + } ).end(). filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); } else if ( nodeName === "div" || nodeName === "span" ) { inline = $target.children( "." + this._inlineClass ); @@ -464,7 +502,11 @@ $.extend( Datepicker.prototype, { prop( "disabled", true ); } this._disabledInputs = $.map( this._disabledInputs, - function( value ) { return ( value === target ? null : value ); } ); // delete entry + + // Delete entry + function( value ) { + return ( value === target ? null : value ); + } ); this._disabledInputs[ this._disabledInputs.length ] = target; }, @@ -492,8 +534,7 @@ $.extend( Datepicker.prototype, { _getInst: function( target ) { try { return $.data( target, "datepicker" ); - } - catch ( err ) { + } catch ( err ) { throw "Missing instance data for this datepicker"; } }, @@ -726,8 +767,7 @@ $.extend( Datepicker.prototype, { $.datepicker._updateAlternate( inst ); $.datepicker._updateDatepicker( inst ); } - } - catch ( err ) { + } catch ( err ) { } } return true; @@ -832,7 +872,8 @@ $.extend( Datepicker.prototype, { numMonths = this._getNumberOfMonths( inst ), cols = numMonths[ 1 ], width = 17, - activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); + activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ), + onUpdateDatepicker = $.datepicker._get( inst, "onUpdateDatepicker" ); if ( activeCell.length > 0 ) { datepicker_handleMouseover.apply( activeCell.get( 0 ) ); @@ -858,11 +899,15 @@ $.extend( Datepicker.prototype, { //assure that inst.yearshtml didn't change. if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { - inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml ); + inst.dpDiv.find( "select.ui-datepicker-year" ).first().replaceWith( inst.yearshtml ); } origyearshtml = inst.yearshtml = null; }, 0 ); } + + if ( onUpdateDatepicker ) { + onUpdateDatepicker.apply( ( inst.input ? inst.input[ 0 ] : null ), [ inst ] ); + } }, // #6694 - don't focus the input if it's already focused @@ -900,7 +945,7 @@ $.extend( Datepicker.prototype, { inst = this._getInst( obj ), isRTL = this._get( inst, "isRTL" ); - while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) { + while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.pseudos.hidden( obj ) ) ) { obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; } @@ -988,9 +1033,7 @@ $.extend( Datepicker.prototype, { if ( this._isDisabledDatepicker( target[ 0 ] ) ) { return; } - this._adjustInstDate( inst, offset + - ( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning - period ); + this._adjustInstDate( inst, offset, period ); this._updateDatepicker( inst ); }, @@ -1037,7 +1080,7 @@ $.extend( Datepicker.prototype, { } inst = this._getInst( target[ 0 ] ); - inst.selectedDay = inst.currentDay = $( "a", td ).html(); + inst.selectedDay = inst.currentDay = parseInt( $( "a", td ).attr( "data-date" ) ); inst.selectedMonth = inst.currentMonth = month; inst.selectedYear = inst.currentYear = year; this._selectDate( id, this._formatDate( inst, @@ -1090,7 +1133,7 @@ $.extend( Datepicker.prototype, { altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); date = this._getDate( inst ); dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); - $( altField ).val( dateStr ); + $( document ).find( altField ).val( dateStr ); } }, @@ -1529,8 +1572,7 @@ $.extend( Datepicker.prototype, { try { return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), offset, $.datepicker._getFormatConfig( inst ) ); - } - catch ( e ) { + } catch ( e ) { // Ignore } @@ -1704,32 +1746,104 @@ $.extend( Datepicker.prototype, { this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), this._getFormatConfig( inst ) ) ); - prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ? - "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" + - " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" : - ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) ); + if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) { + prev = $( "<a>" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all", + "data-handler": "prev", + "data-event": "click", + title: prevText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "e" : "w" ) ) + .text( prevText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + prev = ""; + } else { + prev = $( "<a>" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all ui-state-disabled", + title: prevText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "e" : "w" ) ) + .text( prevText ) + )[ 0 ].outerHTML; + } nextText = this._get( inst, "nextText" ); nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), this._getFormatConfig( inst ) ) ); - next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ? - "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" + - " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" : - ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) ); + if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) { + next = $( "<a>" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all", + "data-handler": "next", + "data-event": "click", + title: nextText + } ) + .append( + $( "<span>" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "w" : "e" ) ) + .text( nextText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + next = ""; + } else { + next = $( "<a>" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all ui-state-disabled", + title: nextText + } ) + .append( + $( "<span>" ) + .attr( "class", "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "w" : "e" ) ) + .text( nextText ) + )[ 0 ].outerHTML; + } currentText = this._get( inst, "currentText" ); gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); currentText = ( !navigationAsDateFormat ? currentText : this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); - controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" + - this._get( inst, "closeText" ) + "</button>" : "" ); - - buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) + - ( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" + - ">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : ""; + controls = ""; + if ( !inst.inline ) { + controls = $( "<button>" ) + .attr( { + type: "button", + "class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all", + "data-handler": "hide", + "data-event": "click" + } ) + .text( this._get( inst, "closeText" ) )[ 0 ].outerHTML; + } + + buttonPanel = ""; + if ( showButtonPanel ) { + buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" ) + .append( isRTL ? controls : "" ) + .append( this._isInRange( inst, gotoDate ) ? + $( "<button>" ) + .attr( { + type: "button", + "class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all", + "data-handler": "today", + "data-event": "click" + } ) + .text( currentText ) : + "" ) + .append( isRTL ? "" : controls )[ 0 ].outerHTML; + } firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); @@ -1817,7 +1931,9 @@ $.extend( Datepicker.prototype, { ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months - "' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date + "' href='#' aria-current='" + ( printDate.getTime() === currentDate.getTime() ? "true" : "false" ) + // mark date as selected for screen reader + "' data-date='" + printDate.getDate() + // store date as data + "'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date printDate.setDate( printDate.getDate() + 1 ); printDate = this._daylightSavingAdjust( printDate ); } @@ -1847,6 +1963,8 @@ $.extend( Datepicker.prototype, { changeMonth = this._get( inst, "changeMonth" ), changeYear = this._get( inst, "changeYear" ), showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), + selectMonthLabel = this._get( inst, "selectMonthLabel" ), + selectYearLabel = this._get( inst, "selectYearLabel" ), html = "<div class='ui-datepicker-title'>", monthHtml = ""; @@ -1856,7 +1974,7 @@ $.extend( Datepicker.prototype, { } else { inMinYear = ( minDate && minDate.getFullYear() === drawYear ); inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); - monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>"; + monthHtml += "<select class='ui-datepicker-month' aria-label='" + selectMonthLabel + "' data-handler='selectMonth' data-event='change'>"; for ( month = 0; month < 12; month++ ) { if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) { monthHtml += "<option value='" + month + "'" + @@ -1891,7 +2009,7 @@ $.extend( Datepicker.prototype, { endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); - inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"; + inst.yearshtml += "<select class='ui-datepicker-year' aria-label='" + selectYearLabel + "' data-handler='selectYear' data-event='change'>"; for ( ; year <= endYear; year++ ) { inst.yearshtml += "<option value='" + year + "'" + ( year === drawYear ? " selected='selected'" : "" ) + @@ -2103,18 +2221,20 @@ $.fn.datepicker = function( options ) { apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); } return this.each( function() { - typeof options === "string" ? - $.datepicker[ "_" + options + "Datepicker" ]. - apply( $.datepicker, [ this ].concat( otherArgs ) ) : + if ( typeof options === "string" ) { + $.datepicker[ "_" + options + "Datepicker" ] + .apply( $.datepicker, [ this ].concat( otherArgs ) ); + } else { $.datepicker._attachDatepicker( this, options ); + } } ); }; $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); -$.datepicker.version = "1.12.1"; +$.datepicker.version = "1.13.0"; return $.datepicker; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/dialog.js b/lib/web/jquery/ui-modules/widgets/dialog.js index 27dccd2ad9362..ca3b0a767ce07 100644 --- a/lib/web/jquery/ui-modules/widgets/dialog.js +++ b/lib/web/jquery/ui-modules/widgets/dialog.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Dialog 1.12.1 + * jQuery UI Dialog 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -41,10 +43,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.dialog", { - version: "1.12.1", + version: "1.13.0", options: { appendTo: "body", autoOpen: true, @@ -289,7 +292,7 @@ $.widget( "ui.dialog", { that._trigger( "focus" ); } ); - // Track the dialog immediately upon openening in case a focus event + // Track the dialog immediately upon opening in case a focus event // somehow occurs outside of the dialog before an element inside the // dialog is focused (#10152) this._makeFocusTarget(); @@ -325,22 +328,23 @@ $.widget( "ui.dialog", { hasFocus.eq( 0 ).trigger( "focus" ); }, - _keepFocus: function( event ) { - function checkFocus() { - var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), - isActive = this.uiDialog[ 0 ] === activeElement || - $.contains( this.uiDialog[ 0 ], activeElement ); - if ( !isActive ) { - this._focusTabbable(); - } + _restoreTabbableFocus: function() { + var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), + isActive = this.uiDialog[ 0 ] === activeElement || + $.contains( this.uiDialog[ 0 ], activeElement ); + if ( !isActive ) { + this._focusTabbable(); } + }, + + _keepFocus: function( event ) { event.preventDefault(); - checkFocus.call( this ); + this._restoreTabbableFocus(); // support: IE // IE <= 8 doesn't prevent moving focus even with event.preventDefault() // so we check again later - this._delay( checkFocus ); + this._delay( this._restoreTabbableFocus ); }, _createWrapper: function() { @@ -369,8 +373,8 @@ $.widget( "ui.dialog", { return; } var tabbables = this.uiDialog.find( ":tabbable" ), - first = tabbables.filter( ":first" ), - last = tabbables.filter( ":last" ); + first = tabbables.first(), + last = tabbables.last(); if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) && !event.shiftKey ) { @@ -481,14 +485,14 @@ $.widget( "ui.dialog", { this.uiDialogButtonPane.remove(); this.uiButtonSet.empty(); - if ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) { + if ( $.isEmptyObject( buttons ) || ( Array.isArray( buttons ) && !buttons.length ) ) { this._removeClass( this.uiDialog, "ui-dialog-buttons" ); return; } $.each( buttons, function( name, props ) { var click, buttonOptions; - props = $.isFunction( props ) ? + props = typeof props === "function" ? { click: props, text: name } : props; @@ -853,6 +857,8 @@ $.widget( "ui.dialog", { return; } + var jqMinor = $.fn.jquery.substring( 0, 4 ); + // We use a delay in case the overlay is created from an // event that we're going to be cancelling (#2804) var isOpening = true; @@ -863,20 +869,28 @@ $.widget( "ui.dialog", { if ( !this.document.data( "ui-dialog-overlays" ) ) { // Prevent use of anchors and inputs - // Using _on() for an event handler shared across many instances is - // safe because the dialogs stack and must be closed in reverse order - this._on( this.document, { - focusin: function( event ) { - if ( isOpening ) { - return; - } + // This doesn't use `_on()` because it is a shared event handler + // across all open modal dialogs. + this.document.on( "focusin.ui-dialog", function( event ) { + if ( isOpening ) { + return; + } - if ( !this._allowInteraction( event ) ) { - event.preventDefault(); - this._trackingInstances()[ 0 ]._focusTabbable(); + var instance = this._trackingInstances()[ 0 ]; + if ( !instance._allowInteraction( event ) ) { + event.preventDefault(); + instance._focusTabbable(); + + // Support: jQuery >=3.4 <3.6 only + // Focus re-triggering in jQuery 3.4/3.5 makes the original element + // have its focus event propagated last, breaking the re-targeting. + // Trigger focus in a delay in addition if needed to avoid the issue + // See https://github.com/jquery/jquery/issues/4382 + if ( jqMinor === "3.4." || jqMinor === "3.5." ) { + instance._delay( instance._restoreTabbableFocus ); } } - } ); + }.bind( this ) ); } this.overlay = $( "<div>" ) @@ -899,7 +913,7 @@ $.widget( "ui.dialog", { var overlays = this.document.data( "ui-dialog-overlays" ) - 1; if ( !overlays ) { - this._off( this.document, "focusin" ); + this.document.off( "focusin.ui-dialog" ); this.document.removeData( "ui-dialog-overlays" ); } else { this.document.data( "ui-dialog-overlays", overlays ); @@ -937,4 +951,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.dialog; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/draggable.js b/lib/web/jquery/ui-modules/widgets/draggable.js index 8c9775138fa66..1cefb7cb603d4 100644 --- a/lib/web/jquery/ui-modules/widgets/draggable.js +++ b/lib/web/jquery/ui-modules/widgets/draggable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Draggable 1.12.1 + * jQuery UI Draggable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -15,6 +15,8 @@ //>>css.structure: ../../themes/base/draggable.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -34,10 +36,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.draggable", $.ui.mouse, { - version: "1.12.1", + version: "1.13.0", widgetEventPrefix: "drag", options: { addClasses: true, @@ -201,7 +204,9 @@ $.widget( "ui.draggable", $.ui.mouse, { this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); + if ( o.cursorAt ) { + this._adjustOffsetFromHelper( o.cursorAt ); + } //Set a containment if given in the options this._setContainment(); @@ -296,7 +301,7 @@ $.widget( "ui.draggable", $.ui.mouse, { if ( ( this.options.revert === "invalid" && !dropped ) || ( this.options.revert === "valid" && dropped ) || - this.options.revert === true || ( $.isFunction( this.options.revert ) && + this.options.revert === true || ( typeof this.options.revert === "function" && this.options.revert.call( this.element, dropped ) ) ) { $( this.helper ).animate( @@ -368,7 +373,7 @@ $.widget( "ui.draggable", $.ui.mouse, { _createHelper: function( event ) { var o = this.options, - helperIsFunction = $.isFunction( o.helper ), + helperIsFunction = typeof o.helper === "function", helper = helperIsFunction ? $( o.helper.apply( this.element[ 0 ], [ event ] ) ) : ( o.helper === "clone" ? @@ -407,7 +412,7 @@ $.widget( "ui.draggable", $.ui.mouse, { if ( typeof obj === "string" ) { obj = obj.split( " " ); } - if ( $.isArray( obj ) ) { + if ( Array.isArray( obj ) ) { obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; } if ( "left" in obj ) { @@ -1116,12 +1121,13 @@ $.ui.plugin.add( "draggable", "snap", { !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) { if ( inst.snapElements[ i ].snapping ) { - ( inst.options.snap.release && + if ( inst.options.snap.release ) { inst.options.snap.release.call( inst.element, event, $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } ) - ) ); + ); + } } inst.snapElements[ i ].snapping = false; continue; @@ -1192,13 +1198,14 @@ $.ui.plugin.add( "draggable", "snap", { } if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) { - ( inst.options.snap.snap && + if ( inst.options.snap.snap ) { inst.options.snap.snap.call( inst.element, event, $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item - } ) ) ); + } ) ); + } } inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first ); @@ -1216,7 +1223,9 @@ $.ui.plugin.add( "draggable", "stack", { ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 ); } ); - if ( !group.length ) { return; } + if ( !group.length ) { + return; + } min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0; $( group ).each( function( i ) { @@ -1247,4 +1256,4 @@ $.ui.plugin.add( "draggable", "zIndex", { return $.ui.draggable; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/droppable.js b/lib/web/jquery/ui-modules/widgets/droppable.js index 4b932aea39a96..986be31ddef12 100644 --- a/lib/web/jquery/ui-modules/widgets/droppable.js +++ b/lib/web/jquery/ui-modules/widgets/droppable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Droppable 1.12.1 + * jQuery UI Droppable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -14,6 +14,8 @@ //>>demos: http://jqueryui.com/droppable/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -29,10 +31,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.droppable", { - version: "1.12.1", + version: "1.13.0", widgetEventPrefix: "drop", options: { accept: "*", @@ -57,7 +60,7 @@ $.widget( "ui.droppable", { this.isover = false; this.isout = true; - this.accept = $.isFunction( accept ) ? accept : function( d ) { + this.accept = typeof accept === "function" ? accept : function( d ) { return d.is( accept ); }; @@ -80,7 +83,9 @@ $.widget( "ui.droppable", { this._addToManager( o.scope ); - o.addClasses && this._addClass( "ui-droppable" ); + if ( o.addClasses ) { + this._addClass( "ui-droppable" ); + } }, @@ -109,7 +114,7 @@ $.widget( "ui.droppable", { _setOption: function( key, value ) { if ( key === "accept" ) { - this.accept = $.isFunction( value ) ? value : function( d ) { + this.accept = typeof value === "function" ? value : function( d ) { return d.is( value ); }; } else if ( key === "scope" ) { @@ -199,14 +204,15 @@ $.widget( "ui.droppable", { inst.accept.call( inst.element[ 0 ], ( draggable.currentItem || draggable.element ) ) && - intersect( + $.ui.intersect( draggable, $.extend( inst, { offset: inst.element.offset() } ), inst.options.tolerance, event ) ) { childrenIntersection = true; - return false; } + return false; + } } ); if ( childrenIntersection ) { return false; @@ -235,7 +241,7 @@ $.widget( "ui.droppable", { }, // Extension points just to make backcompat sane and avoid duplicating logic - // TODO: Remove in 1.13 along with call to it below + // TODO: Remove in 1.14 along with call to it below _addHoverClass: function() { this._addClass( "ui-droppable-hover" ); }, @@ -253,7 +259,7 @@ $.widget( "ui.droppable", { } } ); -var intersect = $.ui.intersect = ( function() { +$.ui.intersect = ( function() { function isOverAxis( x, reference, size ) { return ( x >= reference ) && ( x < ( reference + size ) ); } @@ -361,7 +367,7 @@ $.ui.ddmanager = { return; } if ( !this.options.disabled && this.visible && - intersect( draggable, this, this.options.tolerance, event ) ) { + $.ui.intersect( draggable, this, this.options.tolerance, event ) ) { dropped = this._drop.call( this, event ) || dropped; } @@ -402,7 +408,7 @@ $.ui.ddmanager = { } var parentInstance, scope, parent, - intersects = intersect( draggable, this, this.options.tolerance, event ), + intersects = $.ui.intersect( draggable, this, this.options.tolerance, event ), c = !intersects && this.isover ? "isout" : ( intersects && !this.isover ? "isover" : null ); @@ -494,4 +500,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.droppable; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/menu.js b/lib/web/jquery/ui-modules/widgets/menu.js index fd86527f77a4f..ed7bb9f9de236 100644 --- a/lib/web/jquery/ui-modules/widgets/menu.js +++ b/lib/web/jquery/ui-modules/widgets/menu.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Menu 1.12.1 + * jQuery UI Menu 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -34,10 +36,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.menu", { - version: "1.12.1", + version: "1.13.0", defaultElement: "<ul>", delay: 300, options: { @@ -64,6 +67,7 @@ return $.widget( "ui.menu", { // Flag used to prevent firing of the click handler // as the event bubbles up through nested menus this.mouseHandled = false; + this.lastMousePosition = { x: null, y: null }; this.element .uniqueId() .attr( { @@ -78,6 +82,8 @@ return $.widget( "ui.menu", { // them (focus should always stay on UL during navigation). "mousedown .ui-menu-item": function( event ) { event.preventDefault(); + + this._activateItem( event ); }, "click .ui-menu-item": function( event ) { var target = $( event.target ); @@ -107,36 +113,15 @@ return $.widget( "ui.menu", { } } }, - "mouseenter .ui-menu-item": function( event ) { - - // Ignore mouse events while typeahead is active, see #10458. - // Prevents focusing the wrong item when typeahead causes a scroll while the mouse - // is over an item in the menu - if ( this.previousFilter ) { - return; - } - - var actualTarget = $( event.target ).closest( ".ui-menu-item" ), - target = $( event.currentTarget ); - - // Ignore bubbled events on parent items, see #11641 - if ( actualTarget[ 0 ] !== target[ 0 ] ) { - return; - } - - // Remove ui-state-active class from siblings of the newly focused menu item - // to avoid a jump caused by adjacent elements both having a class with a border - this._removeClass( target.siblings().children( ".ui-state-active" ), - null, "ui-state-active" ); - this.focus( event, target ); - }, + "mouseenter .ui-menu-item": "_activateItem", + "mousemove .ui-menu-item": "_activateItem", mouseleave: "collapseAll", "mouseleave .ui-menu": "collapseAll", focus: function( event, keepActiveItem ) { // If there's already an active item, keep it active // If not, activate the first item - var item = this.active || this.element.find( this.options.items ).eq( 0 ); + var item = this.active || this._menuItems().first(); if ( !keepActiveItem ) { this.focus( event, item ); @@ -162,7 +147,7 @@ return $.widget( "ui.menu", { this._on( this.document, { click: function( event ) { if ( this._closeOnDocumentClick( event ) ) { - this.collapseAll( event ); + this.collapseAll( event, true ); } // Reset the mouseHandled flag @@ -171,6 +156,46 @@ return $.widget( "ui.menu", { } ); }, + _activateItem: function( event ) { + + // Ignore mouse events while typeahead is active, see #10458. + // Prevents focusing the wrong item when typeahead causes a scroll while the mouse + // is over an item in the menu + if ( this.previousFilter ) { + return; + } + + // If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356) + if ( event.clientX === this.lastMousePosition.x && + event.clientY === this.lastMousePosition.y ) { + return; + } + + this.lastMousePosition = { + x: event.clientX, + y: event.clientY + }; + + var actualTarget = $( event.target ).closest( ".ui-menu-item" ), + target = $( event.currentTarget ); + + // Ignore bubbled events on parent items, see #11641 + if ( actualTarget[ 0 ] !== target[ 0 ] ) { + return; + } + + // If the item is already active, there's nothing to do + if ( target.is( ".ui-state-active" ) ) { + return; + } + + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + this._removeClass( target.siblings().children( ".ui-state-active" ), + null, "ui-state-active" ); + this.focus( event, target ); + }, + _destroy: function() { var items = this.element.find( ".ui-menu-item" ) .removeAttr( "role aria-disabled" ), @@ -502,7 +527,7 @@ return $.widget( "ui.menu", { this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); this.activeMenu = currentMenu; - }, this.delay ); + }, all ? 0 : this.delay ); }, // With no arguments, closes the currently active menu - if nothing is active @@ -538,11 +563,7 @@ return $.widget( "ui.menu", { }, expand: function( event ) { - var newItem = this.active && - this.active - .children( ".ui-menu " ) - .find( this.options.items ) - .first(); + var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); @@ -570,21 +591,27 @@ return $.widget( "ui.menu", { return this.active && !this.active.nextAll( ".ui-menu-item" ).length; }, + _menuItems: function( menu ) { + return ( menu || this.element ) + .find( this.options.items ) + .filter( ".ui-menu-item" ); + }, + _move: function( direction, filter, event ) { var next; if ( this.active ) { if ( direction === "first" || direction === "last" ) { next = this.active [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) - .eq( -1 ); + .last(); } else { next = this.active [ direction + "All" ]( ".ui-menu-item" ) - .eq( 0 ); + .first(); } } if ( !next || !next.length || !this.active ) { - next = this.activeMenu.find( this.options.items )[ filter ](); + next = this._menuItems( this.activeMenu )[ filter ](); } this.focus( event, next ); @@ -602,7 +629,13 @@ return $.widget( "ui.menu", { } if ( this._hasScroll() ) { base = this.active.offset().top; - height = this.element.height(); + height = this.element.innerHeight(); + + // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back. + if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) { + height += this.element[ 0 ].offsetHeight - this.element.outerHeight(); + } + this.active.nextAll( ".ui-menu-item" ).each( function() { item = $( this ); return item.offset().top - base - height < 0; @@ -610,7 +643,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ) + this.focus( event, this._menuItems( this.activeMenu ) [ !this.active ? "first" : "last" ]() ); } }, @@ -626,7 +659,13 @@ return $.widget( "ui.menu", { } if ( this._hasScroll() ) { base = this.active.offset().top; - height = this.element.height(); + height = this.element.innerHeight(); + + // jQuery 3.2 doesn't include scrollbars in innerHeight, add it back. + if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) { + height += this.element[ 0 ].offsetHeight - this.element.outerHeight(); + } + this.active.prevAll( ".ui-menu-item" ).each( function() { item = $( this ); return item.offset().top - base + height > 0; @@ -634,7 +673,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ).first() ); + this.focus( event, this._menuItems( this.activeMenu ).first() ); } }, @@ -665,9 +704,10 @@ return $.widget( "ui.menu", { .filter( ".ui-menu-item" ) .filter( function() { return regex.test( - $.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) ); + String.prototype.trim.call( + $( this ).children( ".ui-menu-item-wrapper" ).text() ) ); } ); } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/mouse.js b/lib/web/jquery/ui-modules/widgets/mouse.js index 11a201aa28f42..37f77b3244705 100644 --- a/lib/web/jquery/ui-modules/widgets/mouse.js +++ b/lib/web/jquery/ui-modules/widgets/mouse.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Mouse 1.12.1 + * jQuery UI Mouse 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -13,6 +13,8 @@ //>>docs: http://api.jqueryui.com/mouse/ ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -27,7 +29,8 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; var mouseHandled = false; $( document ).on( "mouseup", function() { @@ -35,7 +38,7 @@ $( document ).on( "mouseup", function() { } ); return $.widget( "ui.mouse", { - version: "1.12.1", + version: "1.13.0", options: { cancel: "input, textarea, button, select, option", distance: 1, @@ -80,7 +83,9 @@ return $.widget( "ui.mouse", { this._mouseMoved = false; // We may have missed mouseup (out of window) - ( this._mouseStarted && this._mouseUp( event ) ); + if ( this._mouseStarted ) { + this._mouseUp( event ); + } this._mouseDownEvent = event; @@ -173,7 +178,11 @@ return $.widget( "ui.mouse", { if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { this._mouseStarted = ( this._mouseStart( this._mouseDownEvent, event ) !== false ); - ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); + if ( this._mouseStarted ) { + this._mouseDrag( event ); + } else { + this._mouseUp( event ); + } } return !this._mouseStarted; @@ -220,7 +229,9 @@ return $.widget( "ui.mouse", { _mouseStart: function( /* event */ ) {}, _mouseDrag: function( /* event */ ) {}, _mouseStop: function( /* event */ ) {}, - _mouseCapture: function( /* event */ ) { return true; } + _mouseCapture: function( /* event */ ) { + return true; + } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/progressbar.js b/lib/web/jquery/ui-modules/widgets/progressbar.js index 89beb58704326..ae2ddd318306e 100644 --- a/lib/web/jquery/ui-modules/widgets/progressbar.js +++ b/lib/web/jquery/ui-modules/widgets/progressbar.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Progressbar 1.12.1 + * jQuery UI Progressbar 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9,9 +9,9 @@ //>>label: Progressbar //>>group: Widgets -// jscs:disable maximumLineLength +/* eslint-disable max-len */ //>>description: Displays a status indicator for loading state, standard percentage, and other progress indicators. -// jscs:enable maximumLineLength +/* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/progressbar/ //>>demos: http://jqueryui.com/progressbar/ //>>css.structure: ../../themes/base/core.css @@ -19,6 +19,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -32,10 +34,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.progressbar", { - version: "1.12.1", + version: "1.13.0", options: { classes: { "ui-progressbar": "ui-corner-all", @@ -175,4 +178,4 @@ return $.widget( "ui.progressbar", { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/resizable.js b/lib/web/jquery/ui-modules/widgets/resizable.js index 741664281d444..9030ad12be820 100644 --- a/lib/web/jquery/ui-modules/widgets/resizable.js +++ b/lib/web/jquery/ui-modules/widgets/resizable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Resizable 1.12.1 + * jQuery UI Resizable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -33,10 +35,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.resizable", $.ui.mouse, { - version: "1.12.1", + version: "1.13.0", widgetEventPrefix: "resize", options: { alsoResize: false, @@ -91,9 +94,15 @@ $.widget( "ui.resizable", $.ui.mouse, { // TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll - el[ scroll ] = 1; - has = ( el[ scroll ] > 0 ); - el[ scroll ] = 0; + try { + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + } catch ( e ) { + + // `el` might be a string, then setting `scroll` will throw + // an error in strict mode; ignore it. + } return has; }, @@ -116,7 +125,8 @@ $.widget( "ui.resizable", $.ui.mouse, { if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) { this.element.wrap( - $( "<div class='ui-wrapper' style='overflow: hidden;'></div>" ).css( { + $( "<div class='ui-wrapper'></div>" ).css( { + overflow: "hidden", position: this.element.css( "position" ), width: this.element.outerWidth(), height: this.element.outerHeight(), @@ -187,15 +197,14 @@ $.widget( "ui.resizable", $.ui.mouse, { _destroy: function() { this._mouseDestroy(); + this._addedHandles.remove(); var wrapper, _destroy = function( exp ) { $( exp ) .removeData( "resizable" ) .removeData( "ui-resizable" ) - .off( ".resizable" ) - .find( ".ui-resizable-handle" ) - .remove(); + .off( ".resizable" ); }; // TODO: Unwrap at same DOM position @@ -226,6 +235,9 @@ $.widget( "ui.resizable", $.ui.mouse, { this._removeHandles(); this._setupHandles(); break; + case "aspectRatio": + this._aspectRatio = !!value; + break; default: break; } @@ -247,6 +259,7 @@ $.widget( "ui.resizable", $.ui.mouse, { } ); this._handles = $(); + this._addedHandles = $(); if ( this.handles.constructor === String ) { if ( this.handles === "all" ) { @@ -258,7 +271,7 @@ $.widget( "ui.resizable", $.ui.mouse, { for ( i = 0; i < n.length; i++ ) { - handle = $.trim( n[ i ] ); + handle = String.prototype.trim.call( n[ i ] ); hname = "ui-resizable-" + handle; axis = $( "<div>" ); this._addClass( axis, "ui-resizable-handle " + hname ); @@ -266,7 +279,10 @@ $.widget( "ui.resizable", $.ui.mouse, { axis.css( { zIndex: o.zIndex } ); this.handles[ handle ] = ".ui-resizable-" + handle; - this.element.append( axis ); + if ( !this.element.children( this.handles[ handle ] ).length ) { + this.element.append( axis ); + this._addedHandles = this._addedHandles.add( axis ); + } } } @@ -332,7 +348,7 @@ $.widget( "ui.resizable", $.ui.mouse, { }, _removeHandles: function() { - this._handles.remove(); + this._addedHandles.remove(); }, _mouseCapture: function( event ) { @@ -712,7 +728,7 @@ $.widget( "ui.resizable", $.ui.mouse, { if ( this._helper ) { - this.helper = this.helper || $( "<div style='overflow:hidden;'></div>" ); + this.helper = this.helper || $( "<div></div>" ).css( { overflow: "hidden" } ); this._addClass( this.helper, this._helper ); this.helper.css( { @@ -769,7 +785,9 @@ $.widget( "ui.resizable", $.ui.mouse, { _propagate: function( n, event ) { $.ui.plugin.call( this, n, [ event, this.ui() ] ); - ( n !== "resize" && this._trigger( n, event, this.ui() ) ); + if ( n !== "resize" ) { + this._trigger( n, event, this.ui() ); + } }, plugins: {}, @@ -890,8 +908,8 @@ $.ui.plugin.add( "resizable", "containment", { co = that.containerOffset; ch = that.containerSize.height; cw = that.containerSize.width; - width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw ); - height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ; + width = ( that._hasScroll( ce, "left" ) ? ce.scrollWidth : cw ); + height = ( that._hasScroll( ce ) ? ce.scrollHeight : ch ); that.parentData = { element: ce, @@ -1198,4 +1216,4 @@ $.ui.plugin.add( "resizable", "grid", { return $.ui.resizable; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/selectable.js b/lib/web/jquery/ui-modules/widgets/selectable.js index 205f0cbacf935..2b4c86df3ae34 100644 --- a/lib/web/jquery/ui-modules/widgets/selectable.js +++ b/lib/web/jquery/ui-modules/widgets/selectable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Selectable 1.12.1 + * jQuery UI Selectable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -15,6 +15,8 @@ //>>css.structure: ../../themes/base/selectable.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -29,10 +31,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.selectable", $.ui.mouse, { - version: "1.12.1", + version: "1.13.0", options: { appendTo: "body", autoRefresh: true, @@ -183,8 +186,12 @@ return $.widget( "ui.selectable", $.ui.mouse, { x2 = event.pageX, y2 = event.pageY; - if ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; } - if ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; } + if ( x1 > x2 ) { + tmp = x2; x2 = x1; x1 = tmp; + } + if ( y1 > y2 ) { + tmp = y2; y2 = y1; y1 = tmp; + } this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } ); this.selectees.each( function() { @@ -307,4 +314,4 @@ return $.widget( "ui.selectable", $.ui.mouse, { } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/selectmenu.js b/lib/web/jquery/ui-modules/widgets/selectmenu.js index e7cec577fcc84..44ce4f2f1e698 100644 --- a/lib/web/jquery/ui-modules/widgets/selectmenu.js +++ b/lib/web/jquery/ui-modules/widgets/selectmenu.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Selectmenu 1.12.1 + * jQuery UI Selectmenu 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -9,9 +9,9 @@ //>>label: Selectmenu //>>group: Widgets -// jscs:disable maximumLineLength +/* eslint-disable max-len */ //>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select. -// jscs:enable maximumLineLength +/* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/selectmenu/ //>>demos: http://jqueryui.com/selectmenu/ //>>css.structure: ../../themes/base/core.css @@ -19,13 +19,14 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery", "./menu", - "../escape-selector", "../form-reset-mixin", "../keycode", "../labels", @@ -39,10 +40,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { - version: "1.12.1", + version: "1.13.0", defaultElement: "<select>", options: { appendTo: null, @@ -97,7 +99,7 @@ return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { this.labels = this.element.labels().attr( "for", this.ids.button ); this._on( this.labels, { click: function( event ) { - this.button.focus(); + this.button.trigger( "focus" ); event.preventDefault(); } } ); @@ -425,7 +427,7 @@ return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { } if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + - $.ui.escapeSelector( this.ids.button ) ).length ) { + $.escapeSelector( this.ids.button ) ).length ) { this.close( event ); } } @@ -656,6 +658,10 @@ return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { var that = this, data = []; options.each( function( index, item ) { + if ( item.hidden ) { + return; + } + data.push( that._parseOption( $( item ), index ) ); } ); this.items = data; @@ -684,4 +690,4 @@ return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { } } ] ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/slider.js b/lib/web/jquery/ui-modules/widgets/slider.js index dc5e66d1faf83..5769899ffea13 100644 --- a/lib/web/jquery/ui-modules/widgets/slider.js +++ b/lib/web/jquery/ui-modules/widgets/slider.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Slider 1.12.1 + * jQuery UI Slider 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -32,10 +34,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.slider", $.ui.mouse, { - version: "1.12.1", + version: "1.13.0", widgetEventPrefix: "slide", options: { @@ -132,7 +135,7 @@ return $.widget( "ui.slider", $.ui.mouse, { options.values = [ this._valueMin(), this._valueMin() ]; } else if ( options.values.length && options.values.length !== 2 ) { options.values = [ options.values[ 0 ], options.values[ 0 ] ]; - } else if ( $.isArray( options.values ) ) { + } else if ( Array.isArray( options.values ) ) { options.values = options.values.slice( 0 ); } } @@ -395,7 +398,7 @@ return $.widget( "ui.slider", $.ui.mouse, { } if ( arguments.length ) { - if ( $.isArray( arguments[ 0 ] ) ) { + if ( Array.isArray( arguments[ 0 ] ) ) { vals = this.options.values; newValues = arguments[ 0 ]; for ( i = 0; i < vals.length; i += 1 ) { @@ -429,7 +432,7 @@ return $.widget( "ui.slider", $.ui.mouse, { } } - if ( $.isArray( this.options.values ) ) { + if ( Array.isArray( this.options.values ) ) { valsLength = this.options.values.length; } @@ -749,4 +752,4 @@ return $.widget( "ui.slider", $.ui.mouse, { } } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/sortable.js b/lib/web/jquery/ui-modules/widgets/sortable.js index 33c2fb7665ce5..d2e1de66b3fa1 100644 --- a/lib/web/jquery/ui-modules/widgets/sortable.js +++ b/lib/web/jquery/ui-modules/widgets/sortable.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Sortable 1.12.1 + * jQuery UI Sortable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -15,6 +15,8 @@ //>>css.structure: ../../themes/base/sortable.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -32,10 +34,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; return $.widget( "ui.sortable", $.ui.mouse, { - version: "1.12.1", + version: "1.13.0", widgetEventPrefix: "sort", ready: false, options: { @@ -195,6 +198,11 @@ return $.widget( "ui.sortable", $.ui.mouse, { // mouseCapture this.refreshPositions(); + //Prepare the dragged items parent + this.appendTo = $( o.appendTo !== "parent" ? + o.appendTo : + this.currentItem.parent() ); + //Create and append the visible helper this.helper = this._createHelper( event ); @@ -209,9 +217,6 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Cache the margins of the original element this._cacheMargins(); - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { @@ -224,25 +229,22 @@ return $.widget( "ui.sortable", $.ui.mouse, { left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, - parent: this._getParentOffset(), // This is a relative to absolute position minus the actual position calculation - // only used for relative positioned helper relative: this._getRelativeOffset() } ); - // Only after we got the offset, we can change the helper's position to absolute + // After we get the helper offset, but before we get the parent offset we can + // change the helper's position to absolute // TODO: Still need to figure out a way to make relative sorting possible this.helper.css( "position", "absolute" ); this.cssPosition = this.helper.css( "position" ); - //Generate the original position - this.originalPosition = this._generatePosition( event ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); + if ( o.cursorAt ) { + this._adjustOffsetFromHelper( o.cursorAt ); + } //Cache the former DOM position this.domPosition = { @@ -259,6 +261,13 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Create the placeholder this._createPlaceholder(); + //Get the next scrolling parent + this.scrollParent = this.placeholder.scrollParent(); + + $.extend( this.offset, { + parent: this._getParentOffset() + } ); + //Set a containment if given in the options if ( o.containment ) { this._setContainment(); @@ -275,13 +284,9 @@ return $.widget( "ui.sortable", $.ui.mouse, { $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body ); } - if ( o.opacity ) { // opacity option - if ( this.helper.css( "opacity" ) ) { - this._storedOpacity = this.helper.css( "opacity" ); - } - this.helper.css( "opacity", o.opacity ); - } - + // We need to make sure to grab the zIndex before setting the + // opacity, because setting the opacity to anything lower than 1 + // causes the zIndex to change from "auto" to 0. if ( o.zIndex ) { // zIndex option if ( this.helper.css( "zIndex" ) ) { this._storedZIndex = this.helper.css( "zIndex" ); @@ -289,6 +294,13 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper.css( "zIndex", o.zIndex ); } + if ( o.opacity ) { // opacity option + if ( this.helper.css( "opacity" ) ) { + this._storedOpacity = this.helper.css( "opacity" ); + } + this.helper.css( "opacity", o.opacity ); + } + //Prepare scrolling if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && this.scrollParent[ 0 ].tagName !== "HTML" ) { @@ -323,77 +335,82 @@ return $.widget( "ui.sortable", $.ui.mouse, { this._addClass( this.helper, "ui-sortable-helper" ); - // Execute the drag once - this causes the helper not to be visiblebefore getting its - // correct position - this._mouseDrag( event ); - return true; + //Move the helper, if needed + if ( !this.helper.parent().is( this.appendTo ) ) { + this.helper.detach().appendTo( this.appendTo ); - }, + //Update position + this.offset.parent = this._getParentOffset(); + } - _mouseDrag: function( event ) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; + //Generate the original position + this.position = this.originalPosition = this._generatePosition( event ); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" ); - //Compute the helpers position - this.position = this._generatePosition( event ); - this.positionAbs = this._convertPositionTo( "absolute" ); + this._mouseDrag( event ); - if ( !this.lastPositionAbs ) { - this.lastPositionAbs = this.positionAbs; - } + return true; - //Do scrolling - if ( this.options.scroll ) { - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { + }, - if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - - event.pageY < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; - } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; - } + _scroll: function( event ) { + var o = this.options, + scrolled = false; - if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - - event.pageX < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; - } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; - } + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { - } else { + if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - + event.pageY < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; + } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; + } - if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); - } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); - } + if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - + event.pageX < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; + } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; + } - if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() - o.scrollSpeed - ); - } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() + o.scrollSpeed - ); - } + } else { + if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); + } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); } - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); + if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() - o.scrollSpeed + ); + } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() + o.scrollSpeed + ); } + } - //Regenerate the absolute position used for position checks + return scrolled; + }, + + _mouseDrag: function( event ) { + var i, item, itemElement, intersection, + o = this.options; + + //Compute the helpers position + this.position = this._generatePosition( event ); this.positionAbs = this._convertPositionTo( "absolute" ); //Set the helper position @@ -404,56 +421,79 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper[ 0 ].style.top = this.position.top + "px"; } - //Rearrange - for ( i = this.items.length - 1; i >= 0; i-- ) { + //Post events to containers + this._contactContainers( event ); - //Cache variables and intersection, continue if no intersection - item = this.items[ i ]; - itemElement = item.item[ 0 ]; - intersection = this._intersectsWithPointer( item ); - if ( !intersection ) { - continue; - } + if ( this.innermostContainer !== null ) { - // Only put the placeholder inside the current Container, skip all - // items from other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this, moving items in "sub-sortables" can cause - // the placeholder to jitter between the outer and inner container. - if ( item.instance !== this.currentContainer ) { - continue; + //Do scrolling + if ( o.scroll ) { + if ( this._scroll( event ) !== false ) { + + //Update item positions used in position checks + this._refreshItemPositions( true ); + + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + } } - // Cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if ( itemElement !== this.currentItem[ 0 ] && - this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && - !$.contains( this.placeholder[ 0 ], itemElement ) && - ( this.options.type === "semi-dynamic" ? - !$.contains( this.element[ 0 ], itemElement ) : - true - ) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { - this._rearrange( event, item ); - } else { - break; + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + + //Rearrange + for ( i = this.items.length - 1; i >= 0; i-- ) { + + //Cache variables and intersection, continue if no intersection + item = this.items[ i ]; + itemElement = item.item[ 0 ]; + intersection = this._intersectsWithPointer( item ); + if ( !intersection ) { + continue; } - this._trigger( "change", event, this._uiHash() ); - break; + // Only put the placeholder inside the current Container, skip all + // items from other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this, moving items in "sub-sortables" can cause + // the placeholder to jitter between the outer and inner container. + if ( item.instance !== this.currentContainer ) { + continue; + } + + // Cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if ( itemElement !== this.currentItem[ 0 ] && + this.placeholder[ intersection === 1 ? + "next" : "prev" ]()[ 0 ] !== itemElement && + !$.contains( this.placeholder[ 0 ], itemElement ) && + ( this.options.type === "semi-dynamic" ? + !$.contains( this.element[ 0 ], itemElement ) : + true + ) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if ( this.options.tolerance === "pointer" || + this._intersectsWithSides( item ) ) { + this._rearrange( event, item ); + } else { + break; + } + + this._trigger( "change", event, this._uiHash() ); + break; + } } } - //Post events to containers - this._contactContainers( event ); - //Interconnect with droppables if ( $.ui.ddmanager ) { $.ui.ddmanager.drag( this, event ); @@ -656,12 +696,12 @@ return $.widget( "ui.sortable", $.ui.mouse, { return false; } - verticalDirection = this._getDragVerticalDirection(); - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical; + horizontalDirection = this.dragDirection.horizontal; return this.floating ? - ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) - : ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); + ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) : + ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); }, @@ -671,8 +711,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.offset.click.top, item.top + ( item.height / 2 ), item.height ), isOverRightHalf = this._isOverAxis( this.positionAbs.left + this.offset.click.left, item.left + ( item.width / 2 ), item.width ), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical, + horizontalDirection = this.dragDirection.horizontal; if ( this.floating && horizontalDirection ) { return ( ( horizontalDirection === "right" && isOverRightHalf ) || @@ -721,7 +761,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { for ( j = cur.length - 1; j >= 0; j-- ) { inst = $.data( cur[ j ], this.widgetFullName ); if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? + queries.push( [ typeof inst.options.items === "function" ? inst.options.items.call( inst.element ) : $( inst.options.items, inst.element ) .not( ".ui-sortable-helper" ) @@ -731,7 +771,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { } } - queries.push( [ $.isFunction( this.options.items ) ? + queries.push( [ typeof this.options.items === "function" ? this.options.items .call( this.element, null, { options: this.options, item: this.currentItem } ) : $( this.options.items, this.element ) @@ -771,7 +811,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { var i, j, cur, inst, targetData, _queries, item, queriesLength, items = this.items, - queries = [ [ $.isFunction( this.options.items ) ? + queries = [ [ typeof this.options.items === "function" ? this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : $( this.options.items, this.element ), this ] ], connectWith = this._connectWith(); @@ -783,7 +823,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { for ( j = cur.length - 1; j >= 0; j-- ) { inst = $.data( cur[ j ], this.widgetFullName ); if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? + queries.push( [ typeof inst.options.items === "function" ? inst.options.items .call( inst.element[ 0 ], event, { item: this.currentItem } ) : $( inst.options.items, inst.element ), inst ] ); @@ -814,26 +854,14 @@ return $.widget( "ui.sortable", $.ui.mouse, { }, - refreshPositions: function( fast ) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, - // the offsetParent's position will change - if ( this.offsetParent && this.helper ) { - this.offset.parent = this._getParentOffset(); - } - + _refreshItemPositions: function( fast ) { var i, item, t, p; for ( i = this.items.length - 1; i >= 0; i-- ) { item = this.items[ i ]; //We ignore calculating positions of all connected containers when we're not over them - if ( item.instance !== this.currentContainer && this.currentContainer && + if ( this.currentContainer && item.instance !== this.currentContainer && item.item[ 0 ] !== this.currentItem[ 0 ] ) { continue; } @@ -851,6 +879,20 @@ return $.widget( "ui.sortable", $.ui.mouse, { item.left = p.left; item.top = p.top; } + }, + + refreshPositions: function( fast ) { + + // Determine whether items are being displayed horizontally + this.floating = this.items.length ? + this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : + false; + + if ( this.innermostContainer !== null ) { + this._refreshItemPositions( fast ); + } + + var i, p; if ( this.options.custom && this.options.custom.refreshContainers ) { this.options.custom.refreshContainers.call( this ); @@ -871,20 +913,20 @@ return $.widget( "ui.sortable", $.ui.mouse, { _createPlaceholder: function( that ) { that = that || this; - var className, + var className, nodeName, o = that.options; if ( !o.placeholder || o.placeholder.constructor === String ) { className = o.placeholder; + nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(); o.placeholder = { element: function() { - var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(), - element = $( "<" + nodeName + ">", that.document[ 0 ] ); + var element = $( "<" + nodeName + ">", that.document[ 0 ] ); - that._addClass( element, "ui-sortable-placeholder", - className || that.currentItem[ 0 ].className ) - ._removeClass( element, "ui-sortable-helper" ); + that._addClass( element, "ui-sortable-placeholder", + className || that.currentItem[ 0 ].className ) + ._removeClass( element, "ui-sortable-helper" ); if ( nodeName === "tbody" ) { that._createTrPlaceholder( @@ -913,9 +955,15 @@ return $.widget( "ui.sortable", $.ui.mouse, { return; } - //If the element doesn't have a actual height by itself (without styles coming - // from a stylesheet), it receives the inline height from the dragged item - if ( !p.height() ) { + // If the element doesn't have a actual height or width by itself (without + // styles coming from a stylesheet), it receives the inline height and width + // from the dragged item. Or, if it's a tbody or tr, it's going to have a height + // anyway since we're populating them with <td>s above, but they're unlikely to + // be the correct height on their own if the row heights are dynamic, so we'll + // always assign the height of the dragged item given forcePlaceholderSize + // is true. + if ( !p.height() || ( o.forcePlaceholderSize && + ( nodeName === "tbody" || nodeName === "tr" ) ) ) { p.height( that.currentItem.innerHeight() - parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) - @@ -990,6 +1038,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { } + this.innermostContainer = innermostContainer; + // If no intersecting containers found, return if ( !innermostContainer ) { return; @@ -1048,9 +1098,11 @@ return $.widget( "ui.sortable", $.ui.mouse, { return; } - itemWithLeastDistance ? - this._rearrange( event, itemWithLeastDistance, null, true ) : + if ( itemWithLeastDistance ) { + this._rearrange( event, itemWithLeastDistance, null, true ); + } else { this._rearrange( event, null, this.containers[ innermostIndex ].element, true ); + } this._trigger( "change", event, this._uiHash() ); this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) ); this.currentContainer = this.containers[ innermostIndex ]; @@ -1058,6 +1110,15 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Update the placeholder this.options.placeholder.update( this.currentContainer, this.placeholder ); + //Update scrollParent + this.scrollParent = this.placeholder.scrollParent(); + + //Update overflowOffset + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + this.overflowOffset = this.scrollParent.offset(); + } + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); this.containers[ innermostIndex ].containerCache.over = 1; } @@ -1067,15 +1128,13 @@ return $.widget( "ui.sortable", $.ui.mouse, { _createHelper: function( event ) { var o = this.options, - helper = $.isFunction( o.helper ) ? + helper = typeof o.helper === "function" ? $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) : ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem ); //Add the helper to the DOM if that didn't happen already if ( !helper.parents( "body" ).length ) { - $( o.appendTo !== "parent" ? - o.appendTo : - this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); + this.appendTo[ 0 ].appendChild( helper[ 0 ] ); } if ( helper[ 0 ] === this.currentItem[ 0 ] ) { @@ -1103,7 +1162,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { if ( typeof obj === "string" ) { obj = obj.split( " " ); } - if ( $.isArray( obj ) ) { + if ( Array.isArray( obj ) ) { obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; } if ( "left" in obj ) { @@ -1383,9 +1442,12 @@ return $.widget( "ui.sortable", $.ui.mouse, { _rearrange: function( event, i, a, hardRefresh ) { - a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) : + if ( a ) { + a[ 0 ].appendChild( this.placeholder[ 0 ] ); + } else { i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ], ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) ); + } //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions @@ -1551,4 +1613,4 @@ return $.widget( "ui.sortable", $.ui.mouse, { } ); -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/spinner.js b/lib/web/jquery/ui-modules/widgets/spinner.js index c59fc57eeb0cf..6d77de7e75442 100644 --- a/lib/web/jquery/ui-modules/widgets/spinner.js +++ b/lib/web/jquery/ui-modules/widgets/spinner.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Spinner 1.12.1 + * jQuery UI Spinner 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -33,9 +35,10 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; -function spinnerModifer( fn ) { +function spinnerModifier( fn ) { return function() { var previous = this.element.val(); fn.apply( this, arguments ); @@ -47,7 +50,7 @@ function spinnerModifer( fn ) { } $.widget( "ui.spinner", { - version: "1.12.1", + version: "1.13.0", defaultElement: "<input>", widgetEventPrefix: "spin", options: { @@ -140,9 +143,13 @@ $.widget( "ui.spinner", { } }, mousewheel: function( event, delta ) { - if ( !delta ) { + var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ); + var isActive = this.element[ 0 ] === activeElement; + + if ( !isActive || !delta ) { return; } + if ( !this.spinning && !this._start( event ) ) { return false; } @@ -340,7 +347,7 @@ $.widget( "ui.spinner", { var incremental = this.options.incremental; if ( incremental ) { - return $.isFunction( incremental ) ? + return typeof incremental === "function" ? incremental( i ) : Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 ); } @@ -438,7 +445,7 @@ $.widget( "ui.spinner", { this.buttons.button( value ? "disable" : "enable" ); }, - _setOptions: spinnerModifer( function( options ) { + _setOptions: spinnerModifier( function( options ) { this._super( options ); } ), @@ -505,7 +512,7 @@ $.widget( "ui.spinner", { this.uiSpinner.replaceWith( this.element ); }, - stepUp: spinnerModifer( function( steps ) { + stepUp: spinnerModifier( function( steps ) { this._stepUp( steps ); } ), _stepUp: function( steps ) { @@ -515,7 +522,7 @@ $.widget( "ui.spinner", { } }, - stepDown: spinnerModifer( function( steps ) { + stepDown: spinnerModifier( function( steps ) { this._stepDown( steps ); } ), _stepDown: function( steps ) { @@ -525,11 +532,11 @@ $.widget( "ui.spinner", { } }, - pageUp: spinnerModifer( function( pages ) { + pageUp: spinnerModifier( function( pages ) { this._stepUp( ( pages || 1 ) * this.options.page ); } ), - pageDown: spinnerModifer( function( pages ) { + pageDown: spinnerModifier( function( pages ) { this._stepDown( ( pages || 1 ) * this.options.page ); } ), @@ -537,7 +544,7 @@ $.widget( "ui.spinner", { if ( !arguments.length ) { return this._parse( this.element.val() ); } - spinnerModifer( this._value ).call( this, newVal ); + spinnerModifier( this._value ).call( this, newVal ); }, widget: function() { @@ -572,4 +579,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.spinner; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/tabs.js b/lib/web/jquery/ui-modules/widgets/tabs.js index 987cb9ef3bfad..072663cce8939 100644 --- a/lib/web/jquery/ui-modules/widgets/tabs.js +++ b/lib/web/jquery/ui-modules/widgets/tabs.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Tabs 1.12.1 + * jQuery UI Tabs 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,12 +17,13 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery", - "../escape-selector", "../keycode", "../safe-active-element", "../unique-id", @@ -34,10 +35,11 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.tabs", { - version: "1.12.1", + version: "1.13.0", delay: 300, options: { active: null, @@ -95,8 +97,8 @@ $.widget( "ui.tabs", { // Take disabling tabs via class attribute from HTML // into account and update option properly. - if ( $.isArray( options.disabled ) ) { - options.disabled = $.unique( options.disabled.concat( + if ( Array.isArray( options.disabled ) ) { + options.disabled = $.uniqueSort( options.disabled.concat( $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { return that.tabs.index( li ); } ) @@ -431,7 +433,6 @@ $.widget( "ui.tabs", { return $( "a", this )[ 0 ]; } ) .attr( { - role: "presentation", tabIndex: -1 } ); this._addClass( this.anchors, "ui-tabs-anchor" ); @@ -503,7 +504,7 @@ $.widget( "ui.tabs", { _setOptionDisabled: function( disabled ) { var currentItem, li, i; - if ( $.isArray( disabled ) ) { + if ( Array.isArray( disabled ) ) { if ( !disabled.length ) { disabled = false; } else if ( disabled.length === this.anchors.length ) { @@ -734,7 +735,7 @@ $.widget( "ui.tabs", { // meta-function to give users option to provide a href string instead of a numerical index. if ( typeof index === "string" ) { index = this.anchors.index( this.anchors.filter( "[href$='" + - $.ui.escapeSelector( index ) + "']" ) ); + $.escapeSelector( index ) + "']" ) ); } return index; @@ -791,7 +792,7 @@ $.widget( "ui.tabs", { disabled = false; } else { index = this._getIndex( index ); - if ( $.isArray( disabled ) ) { + if ( Array.isArray( disabled ) ) { disabled = $.map( disabled, function( num ) { return num !== index ? num : null; } ); @@ -817,7 +818,7 @@ $.widget( "ui.tabs", { if ( $.inArray( index, disabled ) !== -1 ) { return; } - if ( $.isArray( disabled ) ) { + if ( Array.isArray( disabled ) ) { disabled = $.merge( [ index ], disabled ).sort(); } else { disabled = [ index ]; @@ -921,4 +922,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.tabs; -} ) ); +} ); diff --git a/lib/web/jquery/ui-modules/widgets/tooltip.js b/lib/web/jquery/ui-modules/widgets/tooltip.js index f453b456f913a..d3f122331a005 100644 --- a/lib/web/jquery/ui-modules/widgets/tooltip.js +++ b/lib/web/jquery/ui-modules/widgets/tooltip.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Tooltip 1.12.1 + * jQuery UI Tooltip 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -17,6 +17,8 @@ //>>css.theme: ../../themes/base/theme.css ( function( factory ) { + "use strict"; + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -33,19 +35,17 @@ // Browser globals factory( jQuery ); } -}( function( $ ) { +} )( function( $ ) { +"use strict"; $.widget( "ui.tooltip", { - version: "1.12.1", + version: "1.13.0", options: { classes: { "ui-tooltip": "ui-corner-all ui-widget-shadow" }, content: function() { - - // support: IE<9, Opera in jQuery <1.7 - // .text() can't accept undefined, so coerce to a string - var title = $( this ).attr( "title" ) || ""; + var title = $( this ).attr( "title" ); // Escape title, since we're going from an attribute to raw HTML return $( "<a>" ).text( title ).html(); @@ -72,7 +72,7 @@ $.widget( "ui.tooltip", { describedby.push( id ); elem .data( "ui-tooltip-id", id ) - .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); + .attr( "aria-describedby", String.prototype.trim.call( describedby.join( " " ) ) ); }, _removeDescribedBy: function( elem ) { @@ -85,7 +85,7 @@ $.widget( "ui.tooltip", { } elem.removeData( "ui-tooltip-id" ); - describedby = $.trim( describedby.join( " " ) ); + describedby = String.prototype.trim.call( describedby.join( " " ) ); if ( describedby ) { elem.attr( "aria-describedby", describedby ); } else { @@ -331,7 +331,7 @@ $.widget( "ui.tooltip", { position( positionOption.of ); clearInterval( delayedShow ); } - }, $.fx.interval ); + }, 13 ); } this._trigger( "open", event, { tooltip: tooltip } ); @@ -452,6 +452,10 @@ $.widget( "ui.tooltip", { }, _removeTooltip: function( tooltip ) { + + // Clear the interval for delayed tracking tooltips + clearInterval( this.delayedShow ); + tooltip.remove(); delete this.tooltips[ tooltip.attr( "id" ) ]; }, @@ -517,4 +521,4 @@ if ( $.uiBackCompat !== false ) { return $.ui.tooltip; -} ) ); +} ); diff --git a/lib/web/mage/accordion.js b/lib/web/mage/accordion.js index 78d7dae019be2..d6667013626bf 100644 --- a/lib/web/mage/accordion.js +++ b/lib/web/mage/accordion.js @@ -53,7 +53,7 @@ define([ _toggleActivate: function (action, index) { var self = this; - if ($.isArray(index && this.options.multipleCollapsible)) { + if (Array.isArray(index && this.options.multipleCollapsible)) { $.each(index, function () { self.collapsibles.eq(this).collapsible(action); }); diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 7a8973eaaf02b..148fc25f30dd0 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -320,7 +320,7 @@ define([ if (typeof targetEl !== 'function') { targetEl.focus(); - jQuery(targetEl).change(); + jQuery(targetEl).trigger('change'); } }, this)); }, diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index 85ee38f966fa6..64b6039ffebef 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -16,7 +16,7 @@ define([ * @TODO Need to be removed after refactoring all dependent of the form the components */ (function ($) { - $(document).ready(function () { + $(function () { $(document).on('beforeSubmit', function (e) { //eslint-disable-line max-nested-callbacks if (typeof varienGlobalEvents !== 'undefined') { varienGlobalEvents.fireEvent('formSubmit', $(e.target).attr('id')); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index 4a9d0c65e19ea..0b0687e7d329c 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -142,7 +142,7 @@ define([ * @return {*} */ encodeWidgets: function (content) { - return content.gsub(/\{\{widget(.*?)\}\}/i, function (match) { + return content.gsub(/\{\{widget([\S\s]*?)\}\}/i, function (match) { var attributes = wysiwyg.parseAttributesString(match[1]), imageSrc, imageHtml = ''; diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce5Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce5Adapter.js index 4d5f52bc98a9e..7b20e35fb256b 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce5Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce5Adapter.js @@ -174,9 +174,9 @@ define([ controlId.tooltip().state.set('rendered', enabled); if (enabled) { - jQuery(controlId.getEl()).children('button').andSelf().removeAttr('style'); + jQuery(controlId.getEl()).children('button').addBack().removeAttr('style'); } else { - jQuery(controlId.getEl()).children('button').andSelf().attr('style', 'color: inherit;' + + jQuery(controlId.getEl()).children('button').addBack().attr('style', 'color: inherit;' + 'background-color: inherit;' + 'border-color: transparent;' ); diff --git a/lib/web/mage/adminhtml/wysiwyg/widget.js b/lib/web/mage/adminhtml/wysiwyg/widget.js index f39fc22034104..2ba9897bc6402 100644 --- a/lib/web/mage/adminhtml/wysiwyg/widget.js +++ b/lib/web/mage/adminhtml/wysiwyg/widget.js @@ -502,7 +502,7 @@ define([ textarea = document.getElementById(this.widgetTargetId); updateElementAtCursor(textarea, content); varienGlobalEvents.fireEvent('tinymceChange'); - jQuery(textarea).change(); + jQuery(textarea).trigger('change'); } }, diff --git a/lib/web/mage/backend/bootstrap.js b/lib/web/mage/backend/bootstrap.js index 35611204624d5..ca99f5b6ae459 100644 --- a/lib/web/mage/backend/bootstrap.js +++ b/lib/web/mage/backend/bootstrap.js @@ -62,7 +62,7 @@ define([ if (jqXHR.readyState === 4) { try { - jsonObject = $.parseJSON(jqXHR.responseText); + jsonObject = JSON.parse(jqXHR.responseText); if (jsonObject.ajaxExpired && jsonObject.ajaxRedirect) { //eslint-disable-line max-depth window.location.replace(jsonObject.ajaxRedirect); diff --git a/lib/web/mage/backend/form.js b/lib/web/mage/backend/form.js index de270c8ccd202..beb3104f09cb5 100644 --- a/lib/web/mage/backend/form.js +++ b/lib/web/mage/backend/form.js @@ -121,11 +121,11 @@ define([ * @return {String} action url */ _getActionUrl: function (data) { - if ($.type(data) === 'object') { + if (typeof data === 'object') { return this._buildURL(this.oldAttributes.action, data.args); } - return $.type(data) === 'string' ? data : this.oldAttributes.action; + return typeof data === 'string' ? data : this.oldAttributes.action; }, /** diff --git a/lib/web/mage/backend/suggest.js b/lib/web/mage/backend/suggest.js index 18f5861998ffa..316409157e35d 100644 --- a/lib/web/mage/backend/suggest.js +++ b/lib/web/mage/backend/suggest.js @@ -465,7 +465,7 @@ define([ * @private */ _onSelectItem: function (e, item) { - if (item && $.type(item) === 'object' && $(e.target).is(this.element)) { + if (item && typeof item === 'object' && $(e.target).is(this.element)) { this._focusItem(e, { item: item }); @@ -568,7 +568,7 @@ define([ if ((this._term !== term || term.length === 0) && !this.preventBlur) { this._term = term; - if ($.type(term) === 'string' && term.length >= this.options.minLength) { + if (typeof term === 'string' && term.length >= this.options.minLength) { if (this._trigger('search', e) === false) { //eslint-disable-line max-depth return; } @@ -603,7 +603,7 @@ define([ this.element.addClass(this.options.loadingClass); if (this.options.delay) { - if ($.type(this.options.data) !== 'undefined') { + if (typeof this.options.data !== 'undefined') { response(this.filter(this.options.data, term)); } clearTimeout(this._searchTimeout); @@ -705,9 +705,9 @@ define([ var o = this.options, ajaxData; - if ($.isArray(o.source)) { + if (Array.isArray(o.source)) { response(this.filter(o.source, term)); - } else if ($.type(o.source) === 'string') { + } else if (typeof o.source === 'string') { ajaxData = {}; ajaxData[this.options.termAjaxArgument] = term; @@ -721,7 +721,7 @@ define([ response.apply(response, arguments); }, this) }, o.ajaxOptions || {})); - } else if ($.type(o.source) === 'function') { + } else if (typeof o.source === 'function') { o.source.apply(o.source, arguments); } }, @@ -743,7 +743,7 @@ define([ */ filter: function (items, term) { var matcher = new RegExp(term.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&'), 'i'), - itemsArray = $.isArray(items) ? items : $.map(items, function (element) { + itemsArray = Array.isArray(items) ? items : $.map(items, function (element) { return element; }), property = this.options.filterProperty; @@ -781,7 +781,7 @@ define([ * @type {Array} - list of recently searched items * @private */ - this._recentItems = $.isArray(recentItems) ? recentItems : []; + this._recentItems = Array.isArray(recentItems) ? recentItems : []; } this._super(); }, diff --git a/lib/web/mage/backend/tree-suggest.js b/lib/web/mage/backend/tree-suggest.js index 8552c59c9c4e3..347e33710a193 100644 --- a/lib/web/mage/backend/tree-suggest.js +++ b/lib/web/mage/backend/tree-suggest.js @@ -26,7 +26,7 @@ define([ */ treeToList: function (list, nodes, level, path) { $.each(nodes, function () { - if ($.type(this) === 'object') { + if (typeof this === 'object') { list.push({ label: this.label, id: this.id, diff --git a/lib/web/mage/backend/validation.js b/lib/web/mage/backend/validation.js index 2162bf8f928b5..89838b9990dcd 100644 --- a/lib/web/mage/backend/validation.js +++ b/lib/web/mage/backend/validation.js @@ -121,7 +121,7 @@ define([ * @protected */ _create: function () { - if (!this.options.submitHandler && $.type(this.options.submitHandler) !== 'function') { + if (!this.options.submitHandler && typeof this.options.submitHandler !== 'function') { if (!this.options.frontendOnly && this.options.validationUrl) { this.options.submitHandler = $.proxy(this._ajaxValidate, this); } else { diff --git a/lib/web/mage/calendar.css b/lib/web/mage/calendar.css index da19f389d3085..4dbc59bf69f86 100644 --- a/lib/web/mage/calendar.css +++ b/lib/web/mage/calendar.css @@ -266,3 +266,7 @@ width: 200px; /*must have*/ height: 200px; /*must have*/ } + +.ui-timepicker-div .ui_tpicker_unit_hide { + display:none; +} diff --git a/lib/web/mage/calendar.js b/lib/web/mage/calendar.js index 6d9e080f573d5..c255de8e0e6b0 100644 --- a/lib/web/mage/calendar.js +++ b/lib/web/mage/calendar.js @@ -322,7 +322,8 @@ define([ (printDate.getTime() === today.getTime() ? ' ' : '') + (printDate.getTime() === currentDate.getTime() ? ' ui-state-active' : '') + (otherMonth ? ' ui-priority-secondary' : '') + - '" href="#">' + printDate.getDate() + '</a>') + '</td>'; + '" data-date="' + printDate.getDate() + '" href="#">' + + printDate.getDate() + '</a>') + '</td>'; printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } diff --git a/lib/web/mage/collapsible.js b/lib/web/mage/collapsible.js index 511ddf63613b5..bc483edfbf993 100644 --- a/lib/web/mage/collapsible.js +++ b/lib/web/mage/collapsible.js @@ -52,7 +52,7 @@ define([ this.icons = false; if (typeof this.options.icons === 'string') { - this.options.icons = $.parseJSON(this.options.icons); + this.options.icons = JSON.parse(this.options.icons); } this._processPanels(); @@ -377,7 +377,7 @@ define([ } if (typeof animate === 'string') { - animate = $.parseJSON(animate); + animate = JSON.parse(animate); } duration = duration || animate.duration; easing = animate.easing; diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index dca98dd525840..873c66ba3fe76 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -250,7 +250,7 @@ define([ _toggleEnable: function (action, index) { var self = this; - if ($.isArray(index)) { + if (Array.isArray(index)) { $.each(index, function () { self.collapsibles.eq(this).collapsible(action); }); diff --git a/lib/web/magnifier/magnify.js b/lib/web/magnifier/magnify.js index 75f9b8dc77db0..7d193fc1cd970 100644 --- a/lib/web/magnifier/magnify.js +++ b/lib/web/magnifier/magnify.js @@ -294,7 +294,7 @@ define([ }); if (_.isNumber(index)) { - $selectable.eq(index).focus(); + $selectable.eq(index).trigger('focus'); } } } diff --git a/setup/src/Magento/Setup/Console/Command/InstallCommand.php b/setup/src/Magento/Setup/Console/Command/InstallCommand.php index 5176f51baea6d..1f716043c3846 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallCommand.php @@ -33,61 +33,61 @@ class InstallCommand extends AbstractSetupCommand /** * Parameter indicating command whether to cleanup database in the install routine */ - const INPUT_KEY_CLEANUP_DB = 'cleanup-database'; + public const INPUT_KEY_CLEANUP_DB = 'cleanup-database'; /** * Parameter to specify an order_increment_prefix */ - const INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX = 'sales-order-increment-prefix'; + public const INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX = 'sales-order-increment-prefix'; /** * Parameter indicating command whether to install Sample Data */ - const INPUT_KEY_USE_SAMPLE_DATA = 'use-sample-data'; + public const INPUT_KEY_USE_SAMPLE_DATA = 'use-sample-data'; /** * List of comma-separated module names. That must be enabled during installation. * Available magic param all. */ - const INPUT_KEY_ENABLE_MODULES = 'enable-modules'; + public const INPUT_KEY_ENABLE_MODULES = 'enable-modules'; /** * List of comma-separated module names. That must be avoided during installation. * List of comma-separated module names. That must be avoided during installation. * Available magic param all. */ - const INPUT_KEY_DISABLE_MODULES = 'disable-modules'; + public const INPUT_KEY_DISABLE_MODULES = 'disable-modules'; /** * If this flag is enabled, than all your old scripts with format: * InstallSchema, UpgradeSchema will be converted to new db_schema.xml format. */ - const CONVERT_OLD_SCRIPTS_KEY = 'convert-old-scripts'; + public const CONVERT_OLD_SCRIPTS_KEY = 'convert-old-scripts'; /** * Parameter indicating command for interactive setup */ - const INPUT_KEY_INTERACTIVE_SETUP = 'interactive'; + public const INPUT_KEY_INTERACTIVE_SETUP = 'interactive'; /** * Parameter indicating command shortcut for interactive setup */ - const INPUT_KEY_INTERACTIVE_SETUP_SHORTCUT = 'i'; + public const INPUT_KEY_INTERACTIVE_SETUP_SHORTCUT = 'i'; /** * Parameter says that in this mode all destructive operations, like column removal will be dumped */ - const INPUT_KEY_SAFE_INSTALLER_MODE = 'safe-mode'; + public const INPUT_KEY_SAFE_INSTALLER_MODE = 'safe-mode'; /** * Parameter allows to restore data, that was dumped with safe mode before */ - const INPUT_KEY_DATA_RESTORE = 'data-restore'; + public const INPUT_KEY_DATA_RESTORE = 'data-restore'; /** * Regex for sales_order_increment_prefix validation. */ - const SALES_ORDER_INCREMENT_PREFIX_RULE = '/^.{0,20}$/'; + public const SALES_ORDER_INCREMENT_PREFIX_RULE = '/^.{0,20}$/'; /** * Installer service factory @@ -116,6 +116,11 @@ class InstallCommand extends AbstractSetupCommand */ protected $searchConfigOptionsList; + /** + * @var array + */ + private $interactiveSetupUserInput; + /** * Constructor * @@ -228,7 +233,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $consoleLogger = new ConsoleLogger($output); $installer = $this->installerFactory->create($consoleLogger); - $installer->install($input->getOptions()); + $isInteractiveSetup = $input->getOption(self::INPUT_KEY_INTERACTIVE_SETUP) ?? false; + $installer->install($isInteractiveSetup ? array_merge($input->getOptions(), $this->interactiveSetupUserInput) : + $input->getOptions()); $importConfigCommand = $this->getApplication()->find(ConfigImportCommand::COMMAND_NAME); $arrayInput = new ArrayInput([]); @@ -260,6 +267,7 @@ protected function initialize(InputInterface $input, OutputInterface $output) $command .= " --{$key}={$value}"; } $output->writeln("<comment>Try re-running command: php bin/magento setup:install{$command}</comment>"); + $this->interactiveSetupUserInput = $configOptionsToValidate; } $errors = $this->configModel->validate($configOptionsToValidate); @@ -356,7 +364,7 @@ private function interactiveQuestions(InputInterface $input, OutputInterface $ou /** * Runs interactive questions * - * It will ask users for interactive questionst regarding setup configuration. + * It will ask users for interactive questions regarding setup configuration. * * @param InputInterface $input * @param OutputInterface $output @@ -403,14 +411,11 @@ private function askQuestion( $question->setValidator(function ($answer) use ($option, $validateInline) { if ($option instanceof \Magento\Framework\Setup\Option\SelectConfigOption) { - $answer = $option->getSelectOptions()[$answer]; + //If user doesn't provide an input & default value for question is not set, take first option as input. + $answer = $option->getSelectOptions()[$answer] ?? current($option->getSelectOptions()); } - if ($answer == null) { - $answer = ''; - } else { - $answer = trim($answer); - } + $answer = $answer === null ? '' : (is_string($answer) ? trim($answer) : $answer); if ($validateInline) { $option->validate($answer); diff --git a/setup/src/Magento/Setup/Model/ConfigModel.php b/setup/src/Magento/Setup/Model/ConfigModel.php index b1fe7eb238d75..0abb8fa07c70e 100644 --- a/setup/src/Magento/Setup/Model/ConfigModel.php +++ b/setup/src/Magento/Setup/Model/ConfigModel.php @@ -11,6 +11,7 @@ use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\Setup\Option\AbstractConfigOption; use Magento\Framework\Setup\FilePermissions; +use Magento\Setup\Exception as SetupException; class ConfigModel { @@ -68,9 +69,11 @@ public function getAvailableOptions() $optionLists = $this->collector->collectOptionsLists(); foreach ($optionLists as $optionList) { - $optionCollection = array_merge($optionCollection, $optionList->getOptions()); + $optionCollection[] = $optionList->getOptions(); } + $optionCollection = array_merge([], ...$optionCollection); + foreach ($optionCollection as $option) { $currentValue = $this->deploymentConfig->get($option->getConfigPath()); if ($currentValue !== null) { @@ -100,7 +103,7 @@ public function process($inputOptions) foreach ($configData as $config) { $fileConfigStorage = []; if (!$config instanceof ConfigData) { - throw new \Exception( + throw new SetupException( 'In module : ' . $moduleName . 'ConfigOption::createConfig should return an array of ConfigData instances' @@ -134,8 +137,10 @@ public function validate(array $inputOptions) $options = $this->getAvailableOptions(); foreach ($options as $option) { try { - if ($inputOptions[$option->getName()] !== null) { - $option->validate($inputOptions[$option->getName()]); + $inputValue = $inputOptions[$option->getName()] ?? null; + + if ($inputValue !== null) { + $option->validate($inputValue); } } catch (\InvalidArgumentException $e) { $errors[] = $e->getMessage(); @@ -146,10 +151,10 @@ public function validate(array $inputOptions) $options = $this->collector->collectOptionsLists(); foreach ($options as $option) { - $errors = array_merge($errors, $option->validate($inputOptions, $this->deploymentConfig)); + $errors[] = $option->validate($inputOptions, $this->deploymentConfig); } - return $errors; + return array_merge([], ...$errors); } /** @@ -163,7 +168,7 @@ private function checkInstallationFilePermissions() $results = $this->filePermissions->getMissingWritablePathsForInstallation(); if ($results) { $errorMsg = "Missing write permissions to the following paths:" . PHP_EOL . implode(PHP_EOL, $results); - throw new \Exception($errorMsg); + throw new SetupException($errorMsg); } } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList.php b/setup/src/Magento/Setup/Model/ConfigOptionsList.php index f2135d8bf5202..a1179daeb94e3 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList.php @@ -252,7 +252,8 @@ public function validate(array $options, DeploymentConfig $deploymentConfig) $errors[] = $this->validateDbPrefix($options[ConfigOptionsListConstants::INPUT_KEY_DB_PREFIX]); } - if (!$options[ConfigOptionsListConstants::INPUT_KEY_SKIP_DB_VALIDATION]) { + if (isset($options[ConfigOptionsListConstants::INPUT_KEY_SKIP_DB_VALIDATION]) && + !$options[ConfigOptionsListConstants::INPUT_KEY_SKIP_DB_VALIDATION]) { $errors[] = $this->validateDbSettings($options, $deploymentConfig); } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php index 0386353a0b2df..8910298df934a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php @@ -22,7 +22,7 @@ use Magento\Setup\Module\Di\App\Task\OperationFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -62,8 +62,8 @@ class DiCompileCommandTest extends TestCase /** @var OutputInterface|MockObject */ private $outputMock; - /** @var OutputFormatterInterface|MockObject */ - private $outputFormatterMock; + /** @var OutputFormatter */ + private $outputFormatter; /** @var Filesystem\Io\File|MockObject */ private $fileMock; @@ -113,12 +113,10 @@ protected function setUp(): void [ComponentRegistrar::LIBRARY, ['/path/to/library/one', '/path (1)/to/library/two']], ]); - $this->outputFormatterMock = $this->createMock( - OutputFormatterInterface::class - ); + $this->outputFormatter = new OutputFormatter(); $this->outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->outputMock->method('getFormatter') - ->willReturn($this->outputFormatterMock); + ->willReturn($this->outputFormatter); $this->fileMock = $this->getMockBuilder(Filesystem\Io\File::class) ->disableOriginalConstructor() ->getMock(); diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php index a1899e25f1cef..6595d7e64132d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php @@ -10,6 +10,7 @@ use Magento\Backend\Setup\ConfigOptionsList as BackendConfigOptionsList; use Magento\Deploy\Console\Command\App\ConfigImportCommand; use Magento\Framework\Config\ConfigOptionsListConstants as SetupConfigOptionsList; +use Magento\Framework\Console\Cli; use Magento\Framework\Setup\Option\TextConfigOption; use Magento\Setup\Console\Command\AdminUserCreateCommand; use Magento\Setup\Console\Command\InstallCommand; @@ -24,6 +25,7 @@ use Magento\Setup\Model\SearchConfigOptionsList; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Tester\CommandTester; @@ -77,6 +79,11 @@ class InstallCommandTest extends TestCase */ private $adminUserMock; + /** + * @var QuestionHelper + */ + private $questionHelperMock; + protected function setUp(): void { $this->input = [ @@ -102,7 +109,7 @@ protected function setUp(): void $userConfig = $this->createMock(InstallStoreConfigurationCommand::class); $userConfig - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('getOptionsList') ->willReturn($this->getOptionsListUserConfig()); $userConfig @@ -112,7 +119,7 @@ protected function setUp(): void $this->adminUserMock = $this->createMock(AdminUserCreateCommand::class); $this->adminUserMock - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('getOptionsList') ->willReturn($this->getOptionsListAdminUser()); @@ -146,6 +153,10 @@ protected function setUp(): void ->with(ConfigImportCommand::COMMAND_NAME) ->willReturn($this->configImportMock); + $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->command = new InstallCommand( $this->installerFactory, $configModel, @@ -181,6 +192,27 @@ public function testExecute() $commandTester->execute($this->input); } + public function testInteractiveExecute(): void + { + $this->installerFactory + ->expects($this->once()) + ->method('create') + ->willReturn($this->installer); + $this->installer + ->expects($this->once()) + ->method('install'); + $this->questionHelperMock->method('ask'); + $this->helperSetMock + ->method('get') + ->with('question') + ->willReturn($this->questionHelperMock); + $this->command->setHelperSet($this->helperSetMock); + + $commandTester = new CommandTester($this->command); + $commandTester->execute(['--' . InstallCommand::INPUT_KEY_INTERACTIVE_SETUP => true]); + $this->assertEquals(Cli::RETURN_SUCCESS, $commandTester->getStatusCode()); + } + /** * Get list of options for deployment configuration * diff --git a/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php b/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php index 93d8f7802bcda..385624bb34841 100644 --- a/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Validator/DbValidatorTest.php @@ -127,11 +127,13 @@ public function testCheckDatabaseConnectionDbNotAccessible() ->expects($this->atLeastOnce()) ->method('query') ->willReturn($pdo); - $accessibleDbs = ['some_db', 'another_db']; $pdo->expects($this->atLeastOnce()) ->method('fetchAll') - ->willReturn($accessibleDbs); + ->willThrowException( + new \Zend_Db_Statement_Exception("1044 - Access denied for user 'user'@'host' to database 'name'") + ); + $this->dbValidator->checkDatabaseConnection('name', 'host', 'user', 'password'); } diff --git a/setup/src/Magento/Setup/Validator/DbValidator.php b/setup/src/Magento/Setup/Validator/DbValidator.php index 01ed537f887cd..e99e761e1bd0d 100644 --- a/setup/src/Magento/Setup/Validator/DbValidator.php +++ b/setup/src/Magento/Setup/Validator/DbValidator.php @@ -19,7 +19,7 @@ class DbValidator /** * Db prefix max length */ - const DB_PREFIX_LENGTH = 5; + public const DB_PREFIX_LENGTH = 5; /** * DB connection factory @@ -141,17 +141,15 @@ public function checkDatabaseConnectionWithDriverOptions( */ private function checkDatabaseName(\Magento\Framework\DB\Adapter\AdapterInterface $connection, $dbName) { - $query = "SHOW DATABASES"; - $accessibleDbs = $connection->query($query)->fetchAll(\PDO::FETCH_COLUMN, 0); - foreach ($accessibleDbs as $accessibleDbName) { - if ($dbName == $accessibleDbName) { - return true; - } + try { + $connection->query("SHOW TABLES FROM $dbName")->fetchAll(\PDO::FETCH_COLUMN, 0); + return true; + } catch (\Exception $e) { + throw new \Magento\Setup\Exception( + "Database '{$dbName}' does not exist " + . "or specified database server user does not have privileges to access this database." + ); } - throw new \Magento\Setup\Exception( - "Database '{$dbName}' does not exist " - ."or specified database server user does not have privileges to access this database." - ); } /**